This article is participating in the Java Theme Month – Java Debug Notes EventActive link 。

Among the many new features in the new release of Java 8 is support for.net’s much-maligned Lambda expressions.

Of course, we can’t say that Java is an afterthought, Java has its own considerations. I think it’s more likely that Java supports Lambda, but with the same syntax (or programming style) and the same implementation, but with a few tweaks at compile and run time that make Lambda a syntactic sugar for everyone.


The basic use

Let’s start with the simplest use of Lambda expressions:

public class Main { public static void main(String[] args) { new Thread(() -> System.out.println("baidu.com")).start(); }}Copy the code

Those of you who often write demos using Thread() will notice at a glance that the Lambda expression in this example corresponds to the run() method in Runnable. But for those of you who are new to Lambda expressions, it might be hard to understand how this corresponds to the run() method in Runnable. Lambda expressions are used because the Runnable interface is a functional interface.

A functional interface is one that has only one abstract interface that needs to be implemented.

The Runnable object passed in by Thread(Runnable target) is a functional interface that only one method needs to implement. An explicit feature of functional interfaces is the @functionalInterface annotation:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
Copy the code

In addition to the no-argument, single-line case, Lambda expressions can be written in one of the following ways:

  • Execution block

    public class Main { public static void main(String[] args) { new Thread(() -> { System.out.println(“baidu.com”); System.out.println(“baidu.com”); }).start(); }}

  • Accept explicit arguments

    public class Main { public static void main(String[] args) { List list = new LinkedList<>(); // To prepare data … Collections.sort(list, (Integer a, Integer b) -> { System.out.println(a); System.out.println(b); return Integer.compare(a, b); }); }}

  • Accept implicit arguments

    public class Main { public static void main(String[] args) { List list = new LinkedList<>(); // To prepare data … Collections.sort(list, (a, b) -> { System.out.println(a); System.out.println(b); return Integer.compare(a, b); }); }}

  • Automatically process the return value

    public class Main { public static void main(String[] args) { List list = new LinkedList<>(); // To prepare data … Collections.sort(list, (a, b) -> Integer.compare(a, b)); }}

  • Method references

    public class Main { public static void main(String[] args) { List list = new LinkedList<>(); // To prepare data … Collections.sort(list, Integer::compare); }}

That is, the simplest Lambda expression is formalized as: parameters -> an expression. When there are more than one statement to execute, you can use parameters -> {expressions; }; . Lambda expressions also have the ability to derive parameter types and automatically process return values.


Implementation details of Lambda

Before Java 8, there were three main ways to achieve lambda-like effects

  1. Define classes to implement interfaces:

    public class Main { public static void main(String[] args) { Runnable runnable = new RunnableOuterImpl(); new Thread(runnable).start(); }}

  2. Using inner classes:

    public class Main { static final class RunnableImpl implements Runnable { @Override public void run() { System.out.println(“baidu.com”); }}

     public static void main(String[] args) {
         Runnable runnable = new RunnableImpl();
         new Thread(runnable).start();
     }
    Copy the code

    }

  3. Using anonymous inner classes:

    public class Main { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println(“baidu.com”); } }).start(); }}

So which of these three ways is Lambda implemented?

Lambda expressions are, in fact, implemented through inner classes. The complete flow of a Lambda expression is as follows:

  1. At compile time, we create a private static method, and the implementation of the static method is what we do in Lambda expressions

We can verify this by compiling the source code and recompiling it. We can compile the above Lambda version of the Java file, get the corresponding class file, and decompile through Javap. We get the following decompilation:

Compiled from "Main.java"
public class com.baidu.Main {
  public com.baidu.Main();
  public static void main(java.lang.String[]);
  private static void lambda$main$0();
}
Copy the code

You can see the addition of a lambda mainmainmain0 method to the Main class, indicating that the lambda expression is first a private static function. The implementation in this function is what we execute inside the Lambda expression.


  1. At run time, the first time a Lambda expression is executed, an implementation class of the corresponding interface is dynamically generated

Since it is run time, the best way to verify this is through breakpoints. Lambda expressions in the implementation of the breakpoint location, you can see the expression, is to call the Java. Lang. Invoke. LambdaMetafactory metafactory in a class () 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 order to determine the dynamically generated class at run time real, we can perform the Main. Java, specify the JVM parameter – Djdk. Internal. Lambda. DumpProxyClasses export lambda expressions to generate a dynamic proxy class. Then we’ll see a new class file in the same directory as main. class: Main$$Lambda$1.class. Then decompile it by using Javap:

➜ WorkSpace javap -c -p com.bidu.Main\$\ Lambda\$1 Final class com.bidu.Main$$Lambda$1 implements java.lang.Runnable {  private com.baidu.Main$$Lambda$1(); Code: 0: aload_0 1: invokespecial #10 // Method java/lang/Object."<init>":()V 4: return public void run(); Code: 0: invokestatic #17 // Method com/baidu/Main.lambda$main$0:()V 3: return }Copy the code

As you can see, at runtime, the JVM creates an implementation class for the Runnable interface for Lambda expressions. There is a default constructor and a run() method that must be implemented. In the run() method, a static method is called, which is the private static method lambda Mainmainmain0 () that we just generated at compile time.


conclusion

To summarize, the implementation of Lambda expressions is essentially an inner class. Create a private static method in the compiler that holds the code we need to implement in the Lambda expression. Then, at run time, you create an inner class that implements the interface and invoke the compiler generated private static methods in the interface methods.