This article has been authorized personal public account “Hongyang” original release.

Here I am again, returning to writing, aiming for 2 articles by January.

It takes two chapters to clarify that Java Lambda ≠ Android Lambda. This is the first one, and some knowledge of Java Lambda is explained clearly first.

Be patient with this article and you will learn something.

Java Lambda is not equal to an anonymous inner class

Test environment JDK8.

Let’s start with a simple code snippet:

public class TestJavaAnonymousInnerClass { public void test() { Runnable runnable = new Runnable() { @Override public void run() { System.out.println("hello java lambda"); }}; runnable.run(); }}Copy the code

Let me start with a simple question. If I compile javac, how many class files do you think will be generated?

TestJavaLambda$1. Class = TestJavaLambda$1.

Yes, there are two. How can a solid Java foundation be defeated by this kind of problem?

You all know how to write the anonymous inner class above. We can write it as a lambda expression, right? Even the compiler will remind you to use lambda.

public class TestJavaLambda { public void test() { Runnable runnable = () -> { System.out.println("hello java lambda"); }; runnable.run(); }}Copy the code

One more simple question, if I compile javac, how many class files do you think will be generated?

B: well… Are you fucking me? How is this different from the previous question?

Still think it’s two? Shall we try javac again?

Sorry, there’s only one class file left.

So, here’s my new question:

There must be a difference at compile time between Java anonymous inner classes and Lambda expressions. What is the difference?

Second, behind Java Lambda, invokedynamic appears

Test () ¶ Test () ¶ Test () ¶

Test () of the anonymous inner class:

public void test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=3, locals=2, args_size=1 0: new #2 // class com/example/zhanghongyang/blog02/TestJavaAnonymousInnerClass$1 3: dup 4: aload_0 5: invokespecial #3 // Method com/example/zhanghongyang/blog02/TestJavaAnonymousInnerClass$1."<init>":(Lcom/example/zhanghongyang/blog02/TestJavaAnony mousInnerClass;) V 8: astore_1 9: aload_1 10: invokeinterface #4, 1 // InterfaceMethod java/lang/Runnable.run:()V 15: returnCopy the code

Is very simple, is the new of a TestJavaAnonymousInnerClass $1 object, and then call the run () method.

An interesting one is that the constructor calls aload_0, which is the current object this, and passes this. This is the secret that an anonymous inner class can hold an external class object, and actually refers to the current object this.

Let’s look at lambda’s test():

public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=1
         0: invokedynamic #2,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
         5: astore_1
         6: aload_1
         7: invokeinterface #3,  1            // InterfaceMethod java/lang/Runnable.run:()V
        12: return
Copy the code

Instead of an anonymous inner class, there is an Invokedynamic instruction.

If you are familiar with Java bytecode method calls, should often will see a problem: invokespecial, invokevirtual, invokeinterface, invokestatic, invokedynamic and the difference?

Invokespecial in fact, the above section of bytecode also appears, generally refers to the call super method, constructor, private method, etc.; Special calls are methods that identify the caller.

How can the caller be uncertain when calling a method of a class, you might ask?

Yes, such as overloading, can you convert a method call from a parent class to a subclass?

So class non-private member methods, the general call instruction is invokevirtual.

Invokeinterface, invokestatic literal meaning to understand.

If you are confused by bytecode, for example, is the abstract method call the same as the interface method call instruction? Methods with final modifications cannot be copied. Does the instruction change?

The last one is invokedynamic:

It’s rare, and we’ve seen it today, in the case of Java lambda expressions.

For some in-depth research, see here:

Daily asking | written in Java anonymous inner class lambda, really just syntactic sugar?

Now that we know that bytecodes have changed a lot using lambda expressions compared to anonymous inner classes, it becomes even more curious:

What exactly is behind a lambda expression when it is run?

Lambda expressions are not really generated without inner classes

What’s the easiest way to find out what a piece of code looks like at runtime?

B: well… debug?

Now that ides are getting smarter, many times debug compilation details are erased.

An easier way to do this is to type the stack and change the code:

public class TestJavaLambda { public void test() { Runnable runnable = () -> { System.out.println("hello java lambda"); int a = 1/0; }; runnable.run(); } public static void main(String[] args) { new TestJavaLambda().test(); }}Copy the code

Run to see the error stack:

hello java lambda
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.example.zhanghongyang.blog02.TestJavaLambda.lambda$test$0(TestJavaLambda.java:8)
	at com.example.zhanghongyang.blog02.TestJavaLambda.test(TestJavaLambda.java:10)
	at com.example.zhanghongyang.blog02.TestJavaLambda.main(TestJavaLambda.java:14)
Copy the code

Take a look at exactly where our run method is called:

B: well… The final stack is:

TestJavaLambda.lambda$test$0(TestJavaLambda.java:8)
Copy the code

Lambda testTestTest0 method in TestJavaLambda?

Is it because we just sent the compilation to see that we missed, and this method? Let’s decompile again:

javap /Users/zhanghongyang/repo/KotlinLearn/app/src/main/java/com/example/zhanghongyang/blog02/TestJavaLambda.class 
Compiled from "TestJavaLambda.java"
public class com.example.zhanghongyang.blog02.TestJavaLambda {
  public com.example.zhanghongyang.blog02.TestJavaLambda();
  public void test();
  public static void main(java.lang.String[]);
  private void lambda$test$0();
}
Copy the code

This time the javap -p view, -p represents the private method is also output.

There is such a method, look at the bytecode of this method:

private static void lambda$test$0(); descriptor: ()V flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC Code: stack=2, locals=0, args_size=0 0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #8 // String hello java lambda 5: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;) V 8: return LineNumberTable: line 7: 0 line 8: 8Copy the code

This is simply the contents of our lambda expression {} above, printing a line of log.

So this method is called by test? No, there seems to be a problem with this stack. Let’s go back to the stack:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.example.zhanghongyang.blog02.TestJavaLambda.lambda$test$0(TestJavaLambda.java:8)
	at com.example.zhanghongyang.blog02.TestJavaLambda.test(TestJavaLambda.java:10)
	at com.example.zhanghongyang.blog02.TestJavaLambda.main(TestJavaLambda.java:14)
Copy the code

Do you find this stack too simple for our runnable.run call stack?

The stack should be simplified, so let’s add another line of log to see which class I am in when the run() method executes.

We added a line inside the run method

System.out.println(this.getClass().getCanonicalName());
Copy the code

Look at the output:

com.example.zhanghongyang.blog02.TestJavaLambda
Copy the code

HMM.. In fact, we have performed an invalid operation. The current code in this method is put into lambda testTestTest0 (), and the output is TestJavaLambda.

No, I’m gonna blow it up.

Let’s change the method to make this process live longer:

public void test() { Runnable runnable = () -> { System.out.println("hello java lambda"); System.out.println(this.getClass().getCanonicalName()); // Add try {thread.sleep (1000000); } catch (InterruptedException e) { e.printStackTrace(); } int a = 1 / 0; }; runnable.run(); }Copy the code

After the operation…

Cut to the command line and execute the JPS command to check the PID of the current program process:

java zhanghongyang$ jps
99315 GradleDaemon
3682 TestJavaLambda
21298 Main
3685 Jps
3258 GradleDaemon
1275 
3261 KotlinCompileDaemon
Copy the code

See 3682, execute

jstack 3682
Copy the code

How impressive to finally find the stack of the hidden run method in this line.

Here we do not care too much about JPS,jstack these commands, are JDK built-in, you know can check the stack on the line, do not go out to search these two commands, the article read again. Obtain the stack actually also can pass the method calls, small flange is through Reflection. GetCallerClass.

Now we’re one step closer to the truth:

We lambda $test $0 () method is the object: com. Example. Zhanghongyang. Blog02. TestJavaLambda $$lambda $1/1313922862 run method call.

Now we can conclude:

The way lambda expressions are written in this article, at run time, will help us generate intermediate classes, the class name format isThe original class name $$Lambda$number, and then through this intermediate class the final call is made.

Then you might say:

You said run-time build is build? Can you show it to me?

B: well… I’ll show you later.

But let’s think about another problem first.

4. Missing information in the compilation product

We have been saying:

  1. No intermediate classes are generated for Lambda expressions in this example;
  2. The runtime generates intermediate classes for us;

There’s an obvious problem, you didn’t give it to me at compile time, it did at run time; How does it know at runtime whether or not to generate, what class to generate, which class file is the only one you compile, which must contain this information?

Here’s the thing.

At the end of the output:

SourceFile: "TestJavaLambda.java" InnerClasses: public static final #78= #77 of #81; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles BootstrapMethods: 0: #35 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: #36 ()V #37 invokespecial com/example/zhanghongyang/blog02/TestJavaLambda.lambda$test$0:()V #36 ()VCopy the code

Lambda $test$0 contains TestJavaLambda.

Needless to say, the lambda example in this article will add a method lambda to the compiled class file
t e s t test
0(), and carries a message telling the JVM to create an intermediate class at run time.

Actually LambdaMetafactory. Metafactory is used to generate the middle class, in the JDK also have related classes can be viewed, we say it in more detail the follow-up.

Five, take out the middle class to see?

TestJavaLambda$$Lambda$1 we’ve been saying that the runtime generated an intermediate class for us with the name TestJavaLambda$$Lambda$1.

It doesn’t mean I ate two bowls of pasta…

We just said that the JVM generates intermediate classes for us. Actually, Java can take a lot of parameters when it runs. One of the system properties is amazing, and I’ll show you:

java -Djdk.internal.lambda.dumpProxyClasses com.example.zhanghongyang.blog02.TestJavaLambda
Copy the code

With this system property, we can dump the generated classes:

Isn’t that interesting?

Dynamic proxy classes are also generated in the middle and can be exported in a similar way.

This class is not too detailed, we will directly look at the decompilation in the AS:

So simple…

So, the difference between Lambda expressions and anonymous inner classes in this example is pretty big. Just be aware:

  1. Invokedynamic can be used with lambda;
  2. Intermediate classes for Java lambda expressions are not absent, but are generated during the first run.

As for performance issues, the impact should be minimal, almost none.

Here’s a soul question:

What do you think these are for?

After all, I’m working on Android, and I care more about the implementation of Lambda in Android, so I’ll start with Java lambda. As for why I want to see Android lambda implementation, after all, now I often need to bytecode grab pile, custom Transform, It’s important to be clear about the behavior behind some classes.

It is important to note, however, that this article is about how Java lambda works.

Don’t apply to Android! Don’t apply to Android! Don’t apply to Android!

I will write a separate article about Android deicing and D8 in the future. I also remember that a colleague was cheated by Android Lambda once last time, so I will write it together.

This article is based on 1.8.0_181.

See you in the next post!

You can add a wechat official account: Hongyang, so that you can receive articles in the first place.