Lambda Expressions

Backgroud or Issues:

  It is unclear and complex to implement a functional interface(only contains one abstract methond)

     with an anonymous class when you want to pass the function as an argument to a method.

  Lambda Expression allows you to treat functionality as method argument, or code as data.

 

Ideal Use Case for Lambda Expressions

  The following code shows that administrators in an application set some conditions to select members.

  Person class(member class)

 1 public class Person {
 2 
 3   public enum Sex {
 4     MALE, FEMALE
 5   }
 6 
 7   String name;
 8   LocalDate birthday;
 9   Sex gender;
10   String emailAddress;
11 
12   Person(String nameArg, LocalDate birthdayArg,
13          Sex genderArg, String emailArg) {
14     name = nameArg;
15     birthday = birthdayArg;
16     gender = genderArg;
17     emailAddress = emailArg;
18   }
19 
20   public int getAge() {
21     return birthday
22         .until(IsoChronology.INSTANCE.dateNow())
23         .getYears();
24   }
25 
26   public void printPerson() {
27     System.out.println(name + ", " + this.getAge());
28   }
29 
30   public Sex getGender() {
31     return gender;
32   }
33 
34   public String getName() {
35     return name;
36   }
37 
38   public String getEmailAddress() {
39     return emailAddress;
40   }
41 
42   public LocalDate getBirthday() {
43     return birthday;
44   }
45 
46   public static int compareByAge(Person a, Person b) {
47     return a.birthday.compareTo(b.birthday);
48   }
49 
50   public static List<Person> createRoster() {
51 
52     List<Person> roster = new ArrayList<>();
53     roster.add(
54         new Person(
55             "Fred",
56             IsoChronology.INSTANCE.date(1980, 6, 20),
57             Person.Sex.MALE,
58             "fred@example.com"));
59     roster.add(
60         new Person(
61             "Jane",
62             IsoChronology.INSTANCE.date(1990, 7, 15),
63             Person.Sex.FEMALE, "jane@example.com"));
64     roster.add(
65         new Person(
66             "George",
67             IsoChronology.INSTANCE.date(1991, 8, 13),
68             Person.Sex.MALE, "george@example.com"));
69     roster.add(
70         new Person(
71             "Bob",
72             IsoChronology.INSTANCE.date(2000, 9, 12),
73             Person.Sex.MALE, "bob@example.com"));
74 
75     return roster;
76   }
77 }
View Code

   RosterTest class(main class)

  1 public class RosterTest {
  2 
  3   // functional interface of only one abstract method,
  4   // which is so simple that it has no worth in there,
  5   // you can see better usages in Approach 6 and later
  6   interface CheckPerson {
  7     boolean test(Person p);
  8   }
  9 
 10   // Approach 1: Create Methods that Search for Persons that Match One
 11   // Characteristic
 12 
 13   public static void printPersonsOlderThan(List<Person> roster, int age) {
 14     for (Person p : roster) {
 15       if (p.getAge() >= age) {
 16         p.printPerson();
 17       }
 18     }
 19   }
 20   /**
 21    * review
 22    * advantage: simplistic
 23    * disadvantage: the code is brittle, what if you want to print
 24    * members younger than a certain age ?
 25    */
 26 
 27   // Approach 2: Create More Generalized Search Methods
 28 
 29   public static void printPersonsWithinAgeRange(
 30       List<Person> roster, int low, int high) {
 31     for (Person p : roster) {
 32       if (low <= p.getAge() && p.getAge() < high) {
 33         p.printPerson();
 34       }
 35     }
 36   }
 37   /**
 38    * review
 39    * advantage: more generic
 40    * disadvantage: what if you want to print member of a specified sex,
 41    * or other specified criteria ?
 42    * suggest: do not creat another new method with different conditions,
 43    * you should separate the code in a different class.
 44    */
 45 
 46   // Approach 3: Specify Search Criteria Code in a Local Class
 47   // Approach 4: Specify Search Criteria Code in an Anonymous Class
 48   // Approach 5: Specify Search Criteria Code with a Lambda Expression
 49 
 50   public static void printPersons(
 51       List<Person> roster, CheckPerson tester) {
 52     for (Person p : roster) {
 53       if (tester.test(p)) {
 54         p.printPerson();
 55       }
 56     }
 57   }
 58 
 59   // Approach 6: Use Standard Functional Interfaces with Lambda Expressions
 60 
 61   public static void printPersonsWithPredicate(
 62       List<Person> roster, Predicate<Person> tester) {
 63     for (Person p : roster) {
 64       if (tester.test(p)) {
 65         p.printPerson();
 66       }
 67     }
 68   }
 69   /**
 70    * review
 71    * advantage: omit the functional interface through using an
 72    * existing functional interface Predicate<T> in JDK
 73    *
 74    */
 75 
 76   // Approach 7: Use Lambda Expressions Throughout Your Application
 77 
 78   public static void processPersons(
 79       List<Person> roster,
 80       Predicate<Person> tester,
 81       Consumer<Person> block) {
 82     for (Person p : roster) {
 83       if (tester.test(p)) {
 84         block.accept(p);
 85       }
 86     }
 87   }
 88 
 89   // Approach 7, second example
 90 
 91   public static void processPersonsWithFunction(
 92       List<Person> roster,
 93       Predicate<Person> tester,
 94       Function<Person, String> mapper,
 95       Consumer<String> block) {
 96     for (Person p : roster) {
 97       if (tester.test(p)) {
 98         String data = mapper.apply(p);
 99         block.accept(data);
100       }
101     }
102   }
103 
104   // Approach 8: Use Generics More Extensively
105 
106   public static <X, Y> void processElements(
107       Iterable<X> source,
108       Predicate<X> tester,
109       Function<X, Y> mapper,
110       Consumer<Y> block) {
111     for (X p : source) {
112       if (tester.test(p)) {
113         Y data = mapper.apply(p);
114         block.accept(data);
115       }
116     }
117   }
118 
119   public static void main(String... args) {
120 
121     List<Person> roster = Person.createRoster();
122 
123     for (Person p : roster) {
124       p.printPerson();
125     }
126 
127     // Approach 1: Create Methods that Search for Persons that Match One
128     // Characteristic
129 
130     System.out.println("Persons older than 20:");
131     printPersonsOlderThan(roster, 20);
132     System.out.println();
133 
134     // Approach 2: Create More Generalized Search Methods
135 
136     System.out.println("Persons between the ages of 14 and 30:");
137     printPersonsWithinAgeRange(roster, 14, 30);
138     System.out.println();
139 
140     // Approach 3: Specify Search Criteria Code in a Local Class
141 
142     System.out.println("Persons who are eligible for Selective Service:");
143 
144     // the declaration of local class is additional than anonymous class
145     class CheckPersonEligibleForSelectiveService implements CheckPerson {
146       public boolean test(Person p) {
147         return p.getGender() == Person.Sex.MALE
148             && p.getAge() >= 18
149             && p.getAge() <= 25;
150       }
151     }
152     /**
153      * review
154      * advantage: less brittle
155      * disadvantage: have additional code-a new interface and a local lass
156      * for each search
157      * suggest: use an anonymous class, don't need declare a new class for
158      * each search
159      */
160 
161     printPersons(
162         roster, new CheckPersonEligibleForSelectiveService());
163 
164     System.out.println();
165 
166     // Approach 4: Specify Search Criteria Code in an Anonymous Class
167 
168     System.out.println("Persons who are eligible for Selective Service " +
169         "(anonymous class):");
170 
171     printPersons(
172         roster,
173         // more brittle than local class, don't have declaration of class
174         // but bulky because of only one method in the interface
175         new CheckPerson() {
176           public boolean test(Person p) {
177             return p.getGender() == Person.Sex.MALE
178                 && p.getAge() >= 18
179                 && p.getAge() <= 25;
180           }
181         }
182     );
183     /**
184      * review
185      * advantage: reduce the code scale
186      * disadvantage: bulky as for there is only one method in the interface
187      * suggest: use a lambda expression
188      */
189 
190     System.out.println();
191 
192     // Approach 5: Specify Search Criteria Code with a Lambda Expression
193 
194     System.out.println("Persons who are eligible for Selective Service " +
195         "(lambda expression):");
196 
197     printPersons(
198         roster,
199         // lambda expression, omit the name of abstract method
200         (Person p) -> p.getGender() == Person.Sex.MALE
201             && p.getAge() >= 18
202             && p.getAge() <= 25
203     );
204     /**
205      * review
206      * advantage: more concise
207      * disadvantage: not the best practice
208      * suggest: use a standard functional interface provided by java.util.function,
209      * instead of defining a new interface
210      */
211 
212     System.out.println();
213 
214     // Approach 6: Use Standard Functional Interfaces with Lambda
215     // Expressions
216 
217     System.out.println("Persons who are eligible for Selective Service " +
218         "(with Predicate parameter):");
219 
220     printPersonsWithPredicate(
221         roster,
222         p -> p.getGender() == Person.Sex.MALE
223             && p.getAge() >= 18
224             && p.getAge() <= 25
225     );
226 
227     System.out.println();
228 
229     // Approach 7: Use Lambda Expressions Throughout Your Application
230 
231     System.out.println("Persons who are eligible for Selective Service " +
232         "(with Predicate and Consumer parameters):");
233 
234     processPersons(
235         roster,
236         p -> p.getGender() == Person.Sex.MALE
237             && p.getAge() >= 18
238             && p.getAge() <= 25,
239         p -> p.printPerson()
240     );
241 
242     System.out.println();
243 
244     // Approach 7, second example
245 
246     System.out.println("Persons who are eligible for Selective Service " +
247         "(with Predicate, Function, and Consumer parameters):");
248 
249     processPersonsWithFunction(
250         roster,
251         p -> p.getGender() == Person.Sex.MALE
252             && p.getAge() >= 18
253             && p.getAge() <= 25,
254         p -> p.getEmailAddress(),
255         email -> System.out.println(email)
256     );
257 
258     System.out.println();
259 
260     // Approach 8: Use Generics More Extensively
261 
262     System.out.println("Persons who are eligible for Selective Service " +
263         "(generic version):");
264 
265     processElements(
266         roster,
267         p -> p.getGender() == Person.Sex.MALE
268             && p.getAge() >= 18
269             && p.getAge() <= 25,
270         p -> p.getEmailAddress(),
271         email -> System.out.println(email)
272     );
273 
274     System.out.println();
275 
276     // Approach 9: Use Bulk Data Operations That Accept Lambda Expressions
277     // as Parameters
278 
279     System.out.println("Persons who are eligible for Selective Service " +
280         "(with bulk data operations):");
281 
282     roster
283         // Obtain a source of objects
284         .stream()
285         // Filter objects that match a Predicate object
286         .filter(
287             p -> p.getGender() == Person.Sex.MALE
288                 && p.getAge() >= 18
289                 && p.getAge() <= 25)
290         // Map objects to another value as specified by a Function object
291         .map(p -> p.getEmailAddress())
292         // Perform an action as specified by a Consumer object
293         .forEach(email -> System.out.println(email));
294   }
295 }
View Code

 

Syntax of Lambda Expressions

  A comma-separated list of formal parameters enclosed in parentheses. 

  Note:You can omit the data type of the parameters in a lambda expression.

  In addition, you can omit the parentheses if there is only one parameter.

  For example

1 p -> p.getGender() == Person.Sex.MALE 
2     && p.getAge() >= 18
3     && p.getAge() <= 25
View Code

  A -> token

  A body, which consists of a single expression or a statement block.

  For example

 1 //a body of a single expression
 2 p.getGender() == Person.Sex.MALE 
 3     && p.getAge() >= 18
 4     && p.getAge() <= 25
 5 
 6 // a statement block
 7 p -> {
 8     return p.getGender() == Person.Sex.MALE
 9         && p.getAge() >= 18
10         && p.getAge() <= 25;
11 }
12 
13 // a void method invocation
14 email -> System.out.println(email)
View Code

 Note that you can consider lambda expressions as anonymous methods—methods without a name.

 

Accessing Local Variables of the Enclosing Scope

  Lambda expressions have access to local variables of the encolsing scope, but have no shadowing issues, 

  as they are lexically scoped, they don't inherit any names from a supertype or introduce a new level of scope.

  For example

 1 import java.util.function.Consumer;
 2 
 3 public class LambdaScopeTest {
 4 
 5     public int x = 0;
 6 
 7     class FirstLevel {
 8 
 9         public int x = 1;
10 
11         void methodInFirstLevel(int x) {
12             
13             // The following statement causes the compiler to generate
14             // the error "local variables referenced from a lambda expression
15             // must be final or effectively final" in statement A:
16             //
17             // x = 99;
18             
19             Consumer<Integer> myConsumer = (y) -> 
20             {
21                 System.out.println("x = " + x); // Statement A, 23
22                 System.out.println("y = " + y); // 23
23                 System.out.println("this.x = " + this.x); // 1
24                 System.out.println("LambdaScopeTest.this.x = " +
25                     LambdaScopeTest.this.x); // 0
26             };
27 
28             myConsumer.accept(x);
29 
30         }
31     }
32 
33     public static void main(String... args) {
34         LambdaScopeTest st = new LambdaScopeTest();
35         LambdaScopeTest.FirstLevel fl = st.new FirstLevel();
36         fl.methodInFirstLevel(23);
37     }
38 }
View Code

   Note that you cann't substitute x in place of y in the following code, otherwise the compile generates a error 

  "variable x is already defined in method methodInFirstLevel(int)" because

  the lambda expression does not introduce a new level of scoping.

1 Consumer<Integer> myConsumer = (x) -> {  
2     // ...  
3 } 
View Code

   Like local and anonymous classes, a lambda expression can only access local variables

  and parameters of the enclosing block that are final or effectively final.

  The following code is error

1 void methodInFirstLevel(int x) {
2     x = 99;
3     // ...
4 }
View Code

 

Target Typing

  Now recall a lambda expression an two methods 

1 p -> p.getGender() == Person.Sex.MALE
2     && p.getAge() >= 18
3     && p.getAge() <= 25
4 
5 public static void printPersons(List<Person> roster, CheckPerson tester)
6 
7 public void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester)
View Code

  When jvm invokes the printPersons method, the parameter type is CheckerPerson, so as to the lambda expression.

  In the same way, When printPersonsWithPredicate invoked, the target type is Predicate<Person>, so as to the lambda expression.

  The data type that these methods expect is called the target type

  To determine the type of a lambda expression, the Java compiler uses the target type of the context or situation

   in which the lambda expression was found

 

Target Types and Method Arguments 

  Consider the following two functional interfaces

1 public interface Runnable {
2     void run();
3 }
4 
5 public interface Callable<V> {
6     V call();
7 }
View Code

  You have overloaded the method invoke as follows

1 void invoke(Runnable r) {
2     r.run();
3 }
4 
5 <T> T invoke(Callable<T> c) {
6     return c.call();
7 }
View Code 

  Which method will be invoked in the following statement?

1 String s = invoke(() -> "done");
View Code

  The method invoke(Callable<T>) will be invoked because that method returns a value;

  the method invoke(Runnable) does not. In this case, the type of the lambda expression () -> "done" is Callable<T>.

 

Serialization

  strongly discouraged

 

reference from: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#syntax

  

posted @ 2020-08-09 18:59  deep-thinking  阅读(89)  评论(0编辑  收藏  举报