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:
  1. S -> system.out.println (s) A parameter without parentheses.
  2. (x, y) -> Integer.compare(y, x)
  • Optional braces:If the body contains a statement, you do not need braces.
    1. S -> system.out.println (s), no braces required.
    2. (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 , so ps.stream() returns type stream . The map() method receives a functional interface of type Function

, 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 onelambda$main$0The 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.