Contact background
When I first came into contact with lambda expressions, I felt it was quite magical (high bar). Just one () plus -> could pass a piece of code. At that time, I took over my colleague’s code in the company project, and I didn’t know the features of Java8, so I quickly read the book java8 In Action. Decided to write a java8 feature series blog, not only to deepen their impression, but also to share with you, I hope you a lot of advice 😄.
What is Lambda?
Lambda is an anonymous function, and we can think of a Lambda expression as a piece of code that can be passed (passing code as arguments is called behavior parameterization). Lambda allows functions to be passed as arguments to a method. To do so, you need to understand what functional interfaces are, which I won’t cover here, but will cover in the next article.
So first of all, what does lambda look like? Normal writing:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello lambda");
}
}).start();
Copy the code
Lambda method:
new Thread(
() -> System.out.println("hello lambda")
).start();
Copy the code
How’s that? Doesn’t it feel simple? Yes, that’s the beauty of Lambda, which makes writing code simpler and more flexible.
How do I write Lambda?
Optional type declarations:
There is no need to declare parameter types, and the compiler can uniformly identify parameter values. Println (s) -> system.out.println (s) -> system.out.println (s) -> system.out.println (s) -> system.out.println (s) -> system.out.println (s)Optional parameter parentheses:
Parentheses are not required for one parameter, but parentheses are required for multiple parameters. Such as:
- S -> system.out.println (s) A parameter without parentheses.
- (x, y) -> Integer.compare(y, x)
Optional braces:
If the body contains a statement, you do not need braces.- S -> system.out.println (s), no braces required.
- (s) -> { if (s.equals(“s”)){ System.out.println(s); }}; I need curly braces
Optional return keyword:
The compiler automatically returns the value if the body has only one expression return value. The braces need to specify that the expression returns a value.
Lambda body without {} does not return:
Comparator<Integer> com = (x, y) -> Integer.compare(y, x);
Copy the code
Add {} to the Lambda body to add return:
Comparator<Integer> com = (x, y) -> {
int compare = Integer.compare(y, x);
return compare;
};
Copy the code
Type inference
We saw above how a lambda expression should be written, but an important feature of lambda is the optional parameter type declaration, which means you don’t have to write the type of the parameter, so why not? How does it know the type of argument? This is where type inference comes in.
Generic type inference improvements in java8:
- Support for inferencing generic target types from method context
- Support for generic type inference passing to the last method in the method call link
List<Person> ps = ...
Stream<String> names = ps.stream().map(p -> p.getName());
Copy the code
In the above code, ps is of type List
, where the type of T is the type of the Stream element (Person) and the type of R is unknown. Since the target type of the lambda expression remains unknown after overload resolution, we need to derive the type of R: By type checking the lambda expression lambda, we find that the lambda body returns String, so R is of type String, so map() returns Stream
. Most of the time the compiler will parse the right type, but if it can’t parse, we need to:
- Use an explicit lambda expression that provides an explicit type for the parameter P to provide additional type information
- Transform lambda expressions to Function
,> - Provide an actual type for the generic parameter R. (
<String>
The map (p – > p.g etName ()))
Method references
A method reference is a method or constructor used to directly access an existing method or constructor of a class or instance, providing a way to reference the method without executing it. Is a cleaner and easier to understand form of a Lambda expression. It is more readable to use method references directly when only one method call is being made in a Lambda expression. Method references are represented by the “::” operator, with the class or instance name on the left and the method name on the right. (Note: method references :: do not add () to method names, e.g. User::getName)
Several forms of method references:
- Class :: static methods
- Class :: instance methods
- Object :: instance method
For example: Consumer<String> Consumer = (s) -> system.out.println (s); Equivalent to: Consumer<String> Consumer = system. out::println; Function<String, Integer> stringToInteger = (String s) -> integer.parseInt (s); Function<String, Integer> stringToInteger = (String s) -> integer.parseint (s); Function<String, Integer> stringToInteger = Integer::parseInt; For example: BiPredicate<List<String>, String> contains = (List, element) -> List. Contains (element); Equals: BiPredicate<List<String>, String> contains = List::contains;Copy the code
Note:
- The argument list and return value type of the calling method in the Lambda body are kept the same as the function list and return value type of the abstract method in the functional interface
- Use ClassName:: Method when the first argument in the Lambda argument list is the caller to the instance method and the second argument is the argument to the instance method
Constructor reference
Syntax: Class name ::new
Example: Supplier<User> Supplier = ()->new User(); Supplier<User> Supplier = User::new;Copy the code
Note: The constructor methods that need to be invoked are consistent with the argument list of the abstract method in the functional interface.
How is Lambda implemented?
I studied how to write Lambda for a long time, but what is its principle? Let’s take a quick example to see what the truth is:
public class StreamTest {
public static void main(String[] args) {
printString("hello lambda", (String s) -> System.out.println(s));
}
public static void printString(String s, Print<String> print) {
print.print(s);
}
}
@FunctionalInterface
interface Print<T> {
public void print(T t);
}
Copy the code
The above code customizes a functional interface, defines a static method and uses the functional interface to receive parameters. After compiling this class, we go to the terminal interface Javac to compile, and then use Javap (Javap is the JDK’s own anti-parsing tool. From the class bytecode file, the code area (assembly instructions), local variation table, exception table and line offset mapping table, constant pool, and so on corresponding to the current class are back-parsed. Parse, as shown in the figure below:
- Execute javap -p (-p -private displays all classes and members)
If you look at the figure above, you see that compiling a Lambda expression generates one
lambda$main$0
The static method implements the logic of a Lambda expression. Now that we know that the Lambda expression is compiled as a static method, how does this static method get called? Let’s move on - Run the javap -v -p command (-v -verbose displays additional information).
public com.lxs.stream.StreamTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."
":()V
4: return
LineNumberTable:
line 7: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #2 // String hello lambda
2: invokedynamic #3, 0 // InvokeDynamic #0:print:()Lcom/lxs/stream/Print;
7: invokestatic #4 // Method printString:(Ljava/lang/String; Lcom/lxs/stream/Print;) V
10: return
LineNumberTable:
line 10: 0
line 12: 10
public static void printString(java.lang.String, com.lxs.stream.Print<java.lang.String>); descriptor: (Ljava/lang/String; Lcom/lxs/stream/Print;) V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=2 0: aload_1 1: aload_0 2: invokeinterface#5, 2 // InterfaceMethod com/lxs/stream/Print.print:(Ljava/lang/Object;) V
7: return
LineNumberTable:
line 15: 0
line 16: 7
Signature: #19 // (Ljava/lang/String; Lcom/lxs/stream/Print
;) V
private static void lambda$main$0(java.lang.String); descriptor: (Ljava/lang/String;) V flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC Code: stack=2, locals=1, args_size=1 0: getstatic#6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;) V
7: return
LineNumberTable:
line 10: 0
}
SourceFile: "StreamTest.java"
InnerClasses:
public static final #58= #57 of #61; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite;
Method arguments:
#28 (Ljava/lang/Object;) V
#29 invokestatic com/lxs/stream/StreamTest.lambda$main$0:(Ljava/lang/String;) V
#30 (Ljava/lang/String;) V
Copy the code
Only part of the bytecode structure is posted here, not because the constant pool definition is too long.
InnerClasses:
public static final #58= #57 of #61; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite;
Method arguments:
#28 (Ljava/lang/Object;) V
#29 invokestatic com/lxs/stream/StreamTest.lambda$main$0:(Ljava/lang/String;) V
#30 (Ljava/lang/String;) V
Copy the code
Through this period of bytecode structure found is to generate an inner class, using invokestatic calling a LambdaMetafactory. Metafactory method, and the lambda $main $0 as a parameter to pass to go in, Let’s look at the implementation code in the metaFactory method:
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)
throws LambdaConversionException {
AbstractValidatingLambdaMetafactory mf;
mf = new InnerClassLambdaMetafactory(caller, invokedType,
invokedName, samMethodType,
implMethod, instantiatedMethodType,
false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();
}
Copy the code
In the buildCallSite function, it is the spinInnerClass function that builds the inner class. So it generates a StreamTest, right? An inner class such as Lambda$1.class, which is built at run time and is not saved to disk.
@Override CallSite buildCallSite() throws LambdaConversionException { final Class<? > innerClass = spinInnerClass(); The following is omitted... }Copy the code
If you want to see this building, you can set the environment parameter System. SetProperty (” JDK. Internal. Lambda. DumpProxyClasses “, “”); The inner class will be generated on the path you specified. The current runtime path. Let’s see what the generated class looks like. Okay
In javap -v -p StreamTest? Lambda $1. See the class:
{
private com.lxs.stream.StreamTest$$LambdaThe $1(a); descriptor: ()V flags: ACC_PRIVATE Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial#10 // Method java/lang/Object."
":()V
4: return
public void print(java.lang.Object); descriptor: (Ljava/lang/Object;) V flags: ACC_PUBLIC Code: stack=1, locals=2, args_size=2 0: aload_1 1: checkcast#15 // class java/lang/String
4: invokestatic #21 // Method com/lxs/stream/StreamTest.lambda$main$0:(Ljava/lang/String;) V
7: return
RuntimeVisibleAnnotations:
0: # 13 ()
}
Copy the code
See that the lambda$main$0 method is called using the Invokestatic directive in the overridden parint method.
Conclusion: That implements the Lambda expressions, using invokedynamic instruction, the runtime calls LambdaMetafactory. Metafactory dynamically generated inner class, implements the functional interface, and rewrite the functional interface in the method, the method call in Lambda $main $0, The calling method block in the inner class is not dynamically generated, but a static method is compiled in the original class, and the inner class only needs to call that static method.
After you look at the hard point a point of attention oh! More blogs will follow. Please correct any mistakes.