- If implementation of anonymous classes is simple (an interface that contains only one method) then we may pass functionality as argument to another method
- example: action to be taken when a button is clicked
- Lambda expressions enable to treat functionality as method argument or code as data
- Lambda expressions lets us express single-method classes compactly
-
Example: Consider a social networking application
- Feature: administrator can perform any kind of action (sending message on members satisfying certain criteria)
-
Use case:
Name: Perform action on selected members Primary actor: Administrator Preconditions: Administrator is logged into the system Postconditions: Action is performed only on members that fit the specified criteria Main Success Scenario: 1. Adminstrator specifies criteria of members on which to perform a certain action. 2. Administrator specifies an action to perform on these selected members 3. Administrator selects the Submit button. 4. The system finds all members that match the specified criteria. 5. The system performs the specified action on all matching members. Extensions 1a. Administrator has an option to preview those members who match the specified criteria before he or she specifies the action to be performed or before selecting the Submit button. Frequency of Occurrence: Many times during the day -
Members are represented by the following class:
public class Person { public enum Sex { MALE, FEMALE } String name; LocalDate birthday; Sex gender; String emailAddress; public int getAge() { // ... } public void printPerson() { // ... } } -
Let the members of social networking app are stored in a list
List<Person> -
We begin with a naive approach and then and then finish with efficient and concise approach using Lambda expressions
-
Add several methods with each method searching for members that match one characteristic (gender, age)
-
To retrieve members older than a specific age:
public static void printPersonsOlderThan(List<Person> roster, int age) { for (Person p: roster) { if (p.getAge() >= age) { p.printPerson(); } } } -
This application is brittle (?) if newer data types are introduced
- If app is upgraded and we change the structure of Person class to contain different member variables, change in data type or change in algorithm - requires rewriting a lot of API
- Very restrictive: We cannot use this to print members younger than a certain age
-
The following method prints members within a specified range of ages:
public static void printPersonWithinAgeRange(List<Person> roster, int low, int high) { for (Person p: roster) { if (low <= p.getAge() && p.getAge() < high) { p.printPerson(); } } } -
If members of a specified sex must be printed?
-
If combination of specified gender and age range?
-
If class definition is changed by adding relationship status geographical location?
-
Following method prints members that match given criterion
public static void printPersons(List<Person> roster, CheckPerson tester) { for (Person p: roster) { if (tester.test(p)) { p.printPerson(); } } }- Method checks each
Personinstance contained inListroster whether it satisfies search criteria specified inCheckPersontesterby invokingtester.test. Iftester.testreturnstrue,printPersonsis invoked onPersoninstance
- Method checks each
-
Implement
CheckPersoninterface:interface CheckPerson { boolean test(Person p); } -
Implementation of
CheckPerson:class CheckPersonEligibleForSelectiveService implements CheckPerson { public boolean test(Person p) { return p.gender == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; } } -
Usage:
printPersons(roster, new CheckPersonEligibleForSelectiveService()); -
Advantages:
- don't have to change structure of Person
-
Problems:
- Need local class for each search
-
Invocation of method
printPersonsprintPersons( roster, new CheckPerson() { public boolean test(Person p) { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; } } ); -
Advantages:
- New class is not required for each search.
-
Problems:
- Syntax of anonymous class is bulky even though
CheckPersoncontains only one method
- Syntax of anonymous class is bulky even though
-
CheckPersonis a functional interface (interface that contains only one abstract method (static or default)). -
Definition:
-
Omit name of abstract method when it is implemented
printPersons( roster, (Person p) -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 );
-
-
Advantages: Reduces amount of code
-
Interface is too simple to be defined by the programmer so JDK defines standard functional interfaces (defined in
java.util.function) -
Use
java.util.function.Predicate<T>interfaceinterface Predicate<T> { boolean test(T t); } -
Using
Predicate<T>in place ofCheckPersonpublic static void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester) { for (Person p: roster) { if (tester.test(p)) { p.printPerson(); } } } -
Invocation:
printPersonsWithPredicate( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 );
-
Instead of calling
printPersononPerson, can specify different action. Specify the action using lambda expression. -
Use
Consumer<T>interface that containsvoid accept(T t);public static void processPersons( List<Person> roster, Predicate<Person> tester, Consumer<Person> block ) { for (Person p: roster) { if (tester.test(p)) { block.accept(p); } } } -
Invoction:
processPersons( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.printPerson() ); -
If we want to include member profile validation or retrieve contact information, use
Function<T, R>that containsR apply(T t)public static void processPersonsWithFunction( List<Person> roster, Predicate<Person> tester, Function<Person, String> mapper, Consumer<String> block) { for (Person p: roster) { if (tester.test(p)) { String data = mapper.apply(p); block.accept(data); } } } -
The following invocation retrieves email address from each member contained in roster and then prints them
processPersonsWithFunction( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.getEmailAddress(), email -> System.out.println(email) );
-
Generic version of
processPersonsWithFunctionthat accepts collection of elements of any data typepublic static <X, Y> void processElements( Iterable<X> source, Predicate<X> tester, Function<X, Y> mapper, Consumer<Y> block) { for (X p: source) { if (tester.test(p)) { Y data = mapper.apply(p); block.accept(data); } } } -
Invocation:
processElements( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.getEmailAddress(), email -> System.out.println(email) )
-
Aggregate operations
roster .stream() .filter( p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25) .map(p -> p.getEmailAddress()) .forEach(email -> System.out.println(email));- processElements does the following:
- obtains source of objects (Stream stream())
- Filters objects that match a predicate object (Stream filter(Predicate<? super T> predicate))
- Maps objects to another value as specified by a Function object ( Stream map(Function<? super T, ? extends R> mapper))
- Performs an action as specified by a Consumer object (void forEach(Consumer<? super T> action))
- processElements does the following:
-
filter,map,forEach: aggregate opearations- Aggregate operations: these process elements from a stream
- stream: sequence of elements (it is not data structure). It carries elements in collection through pipeline
- pipeline: sequence of stream operations (filter-map-forEach)
- Aggregate operations usually accept Lambda expressions as parameters
- Aggregate Operations
- Aggregate operations: these process elements from a stream
-
Purpose: to process events (keyboard actions, mouse actions, scroll actions) in GUI app we need event handlers.
-
Event handlers are implementations of a particular interface which are often functional interfaces which tend to have only one method
-
Example:
bnt.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { System.out.println("Hello World!"); } });-
EventHandler<ActionEvent>interface contains only one methodhandle(T event) -
Use the following invocation with lambda expression
btn.setOnAction( event -> System.out.println("Hello World!") );
-
-
Lambda expressions consist of the following:
- comma-separated list of formal parameters enclosed in parantheses.
-
Example:
CheckPerson.testcontains one parameter p, which is an instance ofPersonclass
-
Note: we can omit the data type of parameters in lambda expressions. We can omit parantheses for one parameter.
-
Example:
p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25
-
- Arrow token, ->
- Body which contains single expression or statement block
-
Single expression is evaluated and value is returned by Java runtime
-
Example of statement block with return statement:
p -> { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; } -
void method invocation does not have to be enclosed in braces
- Example:
email -> System.out.println(email)
- Example:
-
- comma-separated list of formal parameters enclosed in parantheses.
-
Example:
Calculatorpublic class Calculator { interface IntegerMath { int operation(int a, int b); } public int operateBinary(int a, int b, IntegerMath op) { return op.operation(a, b); } public static void main(String... args) { Calculator myApp = new Calculator(); IntegerMath addition = (a, b) -> a + b; IntegerMath subtraction = (a, b) -> a - b; System.out.println("40 + 2 = " + myApp.operateBinary(40, 2, addition)); System.out.println("20 - 10 = " + myApp.operateBinary(20, 10, subtraction)); } }- Operations
additionandsubtractionis specified by instances ofIntegerMath
- Operations
-
Lambda expressions can capture variables
-
Lambda expreesions do no have shadowing issues (?)
-
Lambda expressions are lexically scoped (they do not inherit any names from supertype or introduce new level of scoping)
-
Lambda expression does not introduce a new level of scoping which enables us to access fields, methods, local variables or enclosed scope.
- To access variables in enclosing class use
this
- To access variables in enclosing class use
-
Example:
import java.util.function.Consumer; public class LambdaScopeTest { public int x = 0; class FirstLevel { public int x = 1; void methodInFirstLevel(int x) { // The following statement causes the compiler to generate // the error "local variables referenced from a lambda expression // must be final or effectively final" in statement A: // // x = 99; Consumer<Integer> myConsumer = (y) -> { System.out.println("x = " + x); // Statement A System.out.println("y = " + y); System.out.println("this.x = " + this.x); System.out.println("LambdaScopeTest.this.x = " + LambdaScopeTest.this.x); }; myConsumer.accept(x); } } public static void main(String... args) { LambdaScopeTest st = new LambdaScopeTest(); LambdaScopeTest.FirstLevel fl = st.new FirstLevel(); fl.methodInFirstLevel(23); } }- If x is substituted in place of y, compiler generates the error "variable x is already defined in method methodInFirstLevel(int)" because lambda expression does not introduce new level of scoping. Lambda expression can directly access
xof methodmethodInFirstLevel - lambda expression can access local variables and parameters of enclosing block that are final or effectively final
-
Example:
void methodInFirstLevel(int x) { x = 99; // ... }FirstLevel.xis not effectively final anymore
-
- If x is substituted in place of y, compiler generates the error "variable x is already defined in method methodInFirstLevel(int)" because lambda expression does not introduce new level of scoping. Lambda expression can directly access
- How to determine the type of a lambda expression?
-
Example:
p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25- If
public static void printPersons(List<Person> roster, CheckPerson tester)is invoked, the type ofpisCheckPersonso the lambda expression is of the typeCheckPerson - If
public void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester)is invoked, the type ofpisPredicate<Person>so the lambda expression is of the typePredicate<Person>
- If
-
- Target type: the data type expected by the methods
- Determining type of lambda expression:
- Type of lambda expression can be found if java compiler can determine the target type
- Variable declarations
- Assignments
- Return statements
- Array initializers
- Method or constructor arguments
- Lambda expression bodies
- Conditional expressions, ?:
- Cast expressions
- Type of lambda expression can be found if java compiler can determine the target type
- For method arguments, compiler uses
- overload resolution
- type argument inference
- Example:
-
Consider the following two functional interfaces
public interface Runnable { void run(); } public interface Callable<V> { V call(); }-
Runnable.rundoes not return a value -
Callable<V>.callreturns a value -
If the method
invokeis overloaded as follows, which method will be invoked?:void invoke(Runnable r) { r.run(); } <T> T invoke(Callable<T> c) { return c.call(); } String s = invoke(() -> "done");invoke(Callable<T>)will be invoked because it returns a value so the type of lambda expression() -> "done"isCallable<T>
-
-
- Lambda expression can be serialized if
- target type is serializable
- captured argument is serializable
- It is discouraged (see inner classes)
-
Lambda expression sometimes calls a method. Method references lets us reference existing methods by name
-
Example:
public class Person { public enum Sex { MALE, FEMALE } String name; LocalDate birthday; Sex gender; String emailAddress; public int getAge() { // ... } public Calendar getBirthday() { return birthday; } public static int compareByAge(Person a, Person b) { return a.birthday.compareTo(b.birthday); } }-
If members are contained in an array and you want to sort the members by age
Person[] rosterAsArray = roster.toArray(new Person[roster.size[]]); class PersonAgeComparator implements Comparator<Person> { public int compare(Person a, Person b) { return a.getBirthday().compareTo(b.getBrithday()); } } Arrays.sort(rosterAsArray, new PersonAgeComparator());-
Method signature of
sortis the following:static <T> void sort(T[] a, Comparator<? super T> c) -
Since
Comparator<? super T>is a functional interface, we can use lambda expressions instead of first defining a class and then initating an new instance of the class that implementsComparatorArrays.sort(rosterAsArray, (Person a, Person b) -> { return a.getBirthday().compareTo(b.getBirthday()); } ); -
We can replace the method definition by existing method
compareByAgein thePersonclassArrays.sort(rosterAsArray, (a, b) -> Person.compareByAge(a, b) ); -
We can use a method reference instead of explicit method invocation
Arrays.sort(rosterAsArray, Person::compareByAge);Person::compareByAgeis semantically similar to(a, b) -> Person.compareByAge(a, b)- formal parameter list is copied from
Comparator<Person>.comparewhich is(Person, Person) - its body calls the method
Person.compareByAge
- formal parameter list is copied from
-
-
| Kind | Example |
|---|---|
| Reference to a static method | ContainingClass::staticMethodName |
| Reference to an instance method of a particular object | containingObject::instanceMethodName |
| Reference to an instance method of an arbitrary object of a particular type | ContainingType::methodName |
| Reference to a constructor | ClassName::new |
- Example:
Person::compareByAge
-
Example:
class ComparisonProvider { public int compareByName(Person a, Person b) { return a.getName().compareTo(b.getName()); } public int compareByAge(Person a, Person b) { return a.getBirthday().compareTo(b.getBirthday()); } } ComparisonProvider myComparisonProvider = new ComparisonProvider(); Arrays.sort(rosterAsArray, myComparisonProvider::comparByName);- JRE infers the arguments type as (Person, Person)
-
Example:
String[] stringArray = {"Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda"}; Arrays.sort(stringArray, String::compareToIgnoreCase);- formal parameter list would be (String a, String b) where a and b are arbitrary names
- Method reference would invoke the method:
a.compareToIgnoreCase(b)
-
Example:
public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>> DEST transferElements(SOURCE sourceCollection, Supplier<DEST> collectionFactory) { DEST result = collectionFactory.get(); for (T t: sourceCollection) { result.add(t); } return result; } -
Functional interface
Suppliercontains one methodgetthat takes no arguments but returns an object -
Invoke lambda expression
Set<Person> rosterSetLambda = transferElements(roster, () -> { return new HashSet<>(); }); -
Using constructor reference instead of lambda expression
Set<Person> rosterSet = transferElements(roster, HashSet::new);-
Type is infered by compiler
-
Alternative definition
Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);
-