Syntax description
A lambda expression consists of the following parts:
1. Comma-separated list of formal parameters in parentheses. The CheckPerson.test method contains a parameter p, which represents an instance of the Person class. Note: The types of parameters in lambda expressions can be omitted; in addition, if there is only one parameter, brackets can also be omitted. For example, the code mentioned in the previous section:
p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25
2. Arrow symbol: ->. Used to separate parameters and function bodies.
3. Function body. Consisting of an expression or block of code. In the example in the previous section, this expression was used:
p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25
If you are using an expression, the Java runtime will calculate and return the value of the expression. In addition, you can also choose to use the return statement in the code block:
p -> { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; }However, the return statement is not an expression. In lambda expressions, the statement needs to be enclosed in curly braces, but there is no need to enclose the statements in curly braces when just calling a method with empty return value, so the following writing method is also correct:
email -> System.out.println(email)
There are many similarities in the declaration of lambda expressions and methods. Therefore, lambda expressions can also be regarded as anonymous methods, that is, methods without a name definition.
The lambda expressions mentioned above are all expressions that only use one parameter as a formal parameter. The following example class, Caulator, demonstrates how to use multiple parameters as formal parameters:
package com.zhyea.zytools;public class Calculator { interface IntegerMath { int operation(int a, int b); } public int operationBinary(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; System.out.println("40 + 2 = " + myApp.operateBinary(40, 2, addition)); System.out.println("20 - 10 = " + myApp.operateBinary(20, 10, subtraction)); }}The operatingBinary method in the code uses two integer parameters to perform arithmetic operations. The arithmetic operation here is itself an example of the IntegerMath interface. In the above program, two arithmetic operations are defined using lambda expressions: addition and subtraction. Executing the program will print the following content:
40 + 2 = 4220 - 10 = 10
Access local variables of external classes
Similar to local classes or anonymous classes, lambda expressions can also access local variables of external classes. The difference is that there is no need to consider issues like overriding when using lambda expressions. The lambda expression is just a lexical concept, which means it does not need to inherit any names from the superclass, nor does it introduce new scopes. That is, the declaration in a lambda expression has the same meaning as the declaration in its external environment. This is demonstrated in the following example:
package com.zhyea.zytools;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 will cause the compiler to report an error "local variables referenced from a lambda expression must be final or effectively final" // 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); }}This code will output the following:
x = 23y = 23this.x = 1LambdaScopeTest.this.x = 0
If you replace the parameter y in the lambda expression myConsumer in the example with x, the compiler will report an error:
Consumer<integer> myConsumer = (x) ->{ // .... };The compiler error message is: "variable x is already defined in method methodInFirstLevel(int)", which means that the variable x has been defined in the method methodInFirstLevel. An error is reported because the lambda expression does not introduce a new scope. Therefore, you can directly access the domain fields, methods and formal parameters of the external class in the lambda expression. In this example, the lambda expression myConsumer directly accesses the parameter x of the method methodInFirstLevel. When accessing members of external classes, this keyword is also used directly. In this example this.x refers to FirstLevel.x.
However, like local or anonymous classes, lambda expressions can only access local variables or external members declared as final (or equivalent to final). For example, we remove the comment before "x=99" in the sample code methodInFirstLevel method:
//The following statement will cause the compiler to report an error "local variables referenced from a lambda expression must be final or effectively final" 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); };Because the value of parameter x is modified in this statement, the parameter x of methodInFirstLevel can no longer be regarded as final. Therefore, the java compiler will report an error like "local variables referenced from a lambda expression must be final or effectively final" where the lambda expression accesses the local variable x.
Target Type
How to determine the type of lambda expression? Let’s take a look at the code for filtering military personnel of appropriate age:
p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25
This code has been used in two places:
public static void printPersons(List<Person> roster, CheckPerson tester) - Solution 3
public void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester) - Plan VI
When calling the printPersons method, this method expects a CheckPerson type parameter. At this time, the above expression is an CheckPerson type expression. When calling the printPersonsWithPredicate method, a parameter of type Predicate<Person> is expected. At this time, the same expression is of type Predicate<Person>. Like this, the type determined by the type expected by the method is called the target type (actually, I think the type inference in scala is more appropriate here). The java compiler determines the type of a lambda expression through the context of the target type or the position when discovering the lambda expression. This means that Lambda expressions can only be used where the Java compiler can infer the type:
Target type and method parameters
For method parameters, the Java compiler also needs to rely on two language features to determine the target type: overload parsing and type parameter inference.
Take a look at the following two functional interfaces (java.lang.Runnable and java.util.concurrent.Callable<V>):
public interface Runnable { void run(); } public interface Callable<v> { V call(); }The Runnable.run() method does not return a value, while the Callable.call() method has it.
Suppose we overload the invoke method like the following:
void invoke(Runnable r) { r.run(); } <t> T invoke(Callable<t> c) { return c.call(); }So which method will be called in the following statement:
String s = invoke(() -> "done");
Invoke(Callable<T>) is called because this method has a return value, while invoke(Runnable<T>) does not return value. In this case the type of the lambda expression (() -> "done") is Callable<T>.
Serialization
If the target type of a lambda expression and the types of parameters it calls are serializable, then the lambda expression is also serializable. However, like inner classes, serialization of lambda expressions is strongly not recommended.