Java internal class, I believe you have used it, but most students may not have a deep understanding of it, just rely on memory to complete the daily work, but can not be integrated, encountered strange problems are difficult to have ideas to solve. This article takes you through some aspects of Java inner classes. Note: This discussion is based on JDK version 1.8.0_191

The opening question

I always think that technology is a tool that must be landed and solve certain problems. Therefore, we can deepen our understanding by first throwing out problems and then solving these problems in the process, which is the easiest to gain. So, a couple of questions. (If you’ve already thought about these questions and know the answers, you can close this article.)

  • Why do WE need inner classes?
  • Why do inner classes (anonymous inner classes, local inner classes) hold references to outer classes?
  • Why does an anonymous inner class need to be final when it uses a local variable in an outer class method?
  • How do I create an instance of an inner class, and how do I inherit an inner class?
  • How are Lambda expressions implemented?

Why do WE need inner classes?

To answer this question, what is an inner class? We know that Java has three types of inner classes

Ordinary inner class

Public class Demo {// Common internal class public class DemoRunnable implements Runnable {@override public voidrun() {}}}Copy the code

Anonymous inner class

Public class Demo {// Anonymous inner class private Runnable Runnable = newRunnable() {
        @Override
        public void run() {}}; }Copy the code

A local inner class within a method

Public class Demo {// Local inner class public voidwork() {
        class InnerRunnable implements Runnable {
            @Override
            public void run() { } } InnerRunnable runnable = new InnerRunnable(); }}Copy the code

These three forms of inner class, we have certainly used, but the technology at the beginning of the design must also be used to solve a problem or a pain point, so you can think about the inner class compared to the external defined class what advantages? Let’s illustrate this with a small example

public class Worker {
    private List<Job> mJobList = new ArrayList<>();

    public void addJob(Runnable task) {
        mJobList.add(new Job(task));
    }

    private class Job implements Runnable {
        Runnable task;
        public  Job(Runnable task) {
            this.task = task;
        }

        @Override
        public void run() {
            runnable.run();
            System.out.println("left job size : "+ mJobList.size()); }}}Copy the code

It defines a Worker class, exposes an addJob method and a parameter task whose type is Runnable, and then defines an internal class Job to encapsulate task. The Job is private, so the outside world can’t perceive the existence of the Job, so it has the first advantage of the internal class.

  • Inner classes provide better encapsulation, cohesion, and masking of details

In the Job run method, we print the number of remaining jobs in the external Worker’s mJobList list. It is ok to write the code like this, but think about it for a moment. How does the inner class get the member variables of the external class? I’m going to keep you in suspense, but you can already point to the second advantage of inner classes.

  • Inner classes naturally have the ability to access external class member variables

Inner classes are the two main advantages above. Of course, there are some other small advantages, such as multiple inheritance, can be used to consolidate logic in a class for easy maintenance, etc., these are personal opinions, leave them aside.

Let’s move on to the second question!!

Why do inner classes (anonymous inner classes, local inner classes) hold references to outer classes?

This question, I seem to be a bar, you don’t worry, in fact, I want to ask how the inner class Java is implemented. Again, let’s take a normal inner class as an example

An implementation of a normal inner class

Public class Demo {// Common internal class public class DemoRunnable implements Runnable {@override public voidrun() {}}}Copy the code

Go to the Demo. Java folder and run javac Demo. Java. In the same directory as Demo, two class files are generated

Demo.class is easy to understand, another class

Demo$DemoRunnable.class
Copy the code

Is our internal class compiled, its naming is also regular, external class name Demo+$+ internal class name DemoRunnable. View the decomcompiled code (IntelliJ IDEA itself supports, directly view the class file can be)

package inner;

public class Demo$DemoRunnable implements Runnable {
    public Demo$DemoRunnable(Demo var1) {
        this.this$0 = var1;
    }

    public void run() {}}Copy the code

The generated class has only one constructor, with arguments of type Demo, stored in the this$0 field of the inner class itself. By this point we can already see that the inner class holds a reference to the outer class that is passed in through this constructor, which is a strong reference.

Test our ideas

How do you test that? We need to add a method to the demo. class class to instantiate this DemoRunnable internal class object

   // Demo.java
    public void run() {
        DemoRunnable demoRunnable = new DemoRunnable();
        demoRunnable.run();
    }
Copy the code

Javap-verbose Demo. Javap-verbose Demo. Javap-verbose Demo. Javap-verbose Demo. -)

  public void run();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #2 // class inner/Demo$DemoRunnable
         3: dup
         4: aload_0
         5: invokespecial #3 // Method inner/Demo$DemoRunnable."
      
       ":(Linner/Demo;) V
      
         8: astore_1
         9: aload_1
        10: invokevirtual #4 // Method inner/Demo$DemoRunnable.run:()V
        13: return

Copy the code
  • With the new directive, we created a Demo$DemoRunnable object
  • The aload_0 directive loads the external class Demo object itself into the stack frame
  • Call the init method of the Demo$DemoRunnable class, notice that the Demo object is passed in as an argument

At this point it is clear that the outer class object itself is passed as a parameter to the inner class constructor, as we guessed above.

Implementation of anonymous inner classes

Public class Demo {// Anonymous inner class private Runnable Runnable = newRunnable() {
        @Override
        public void run() {}}; }Copy the code

Javac demo.java is also executed, this time generating an additional Demo$1.class, decompile to see the code

package inner;

class DemoThe $1 implements Runnable {
    DemoThe $1(Demo var1) {
        this.this$0 = var1;
    }

    public void run() {}}Copy the code

As you can see, the anonymous inner class is implemented basically the same as the normal inner class, except that the compiler automatically spells it with a name, so the anonymous inner class cannot customize the constructor because the name is determined after the compilation. Method local inner class, I won’t go over it here, but it’s the same principle, so you can try it out. So we’ve solved the second question. Let’s look at the third question.

Why does an anonymous inner class need to be final when it uses a local variable in an outer class method?

To be clear, there is something wrong with this question. What is the problem? Because it is not necessary to declare final in java8. Let’s look at an example

   // Demo.java
    public void run() {
        int age = 10;
        Runnable runnable = new Runnable() {
            @Override
            public void run() { int myAge = age + 1; System.out.println(myAge); }}; }Copy the code

The anonymous inner class object runnable uses the age local variable in the outer class method. The compiler runs perfectly fine, and age is not final. So let’s try changing age again in the run method

    public void run() {
        int age = 10;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                int myAge = age + 1;
                System.out.println(myAge);
                age = 20;   // error
            }
        };
    }
Copy the code

Age is access from inner class, need to be final or effectively final. Clearly, the compiler is smart, because our first example didn’t change the age value, so the compiler thought it was effectively final and safe to compile, whereas the second example tried to change the age value, and the compiler immediately reported an error.

How are external class variables passed to inner classes?

The types of variables are described in three cases

Non-final local variables

We remove the code that attempts to modify age, and then execute javac demo. Java to see the Demo$1.class implementation code

package inner;

class DemoThe $1 implements Runnable {
    DemoThe $1(Demo var1, int var2) {
        this.this$0 = var1;
        this.val$age = var2;
    }

    public void run() {
        int var1 = this.val$age+ 1; System.out.println(var1); }}Copy the code

You can see that non-final local variables are passed in by way of constructors.

Final local variable

Change the age to final

    public void run() {
        final int age = 10;
        Runnable runnable = new Runnable() {
            @Override
            public void run() { int myAge = age + 1; System.out.println(myAge); }}; }Copy the code

Also execute javac demo. Java to see the implementation code for Demo$1.class

class DemoThe $1 implements Runnable {
    DemoThe $1(Demo var1) {
        this.this$0 = var1;
    }

    public void run() { byte var1 = 11; System.out.println(var1); }}Copy the code

As you can see, the compiler wisely optimizes it. Age is final, so it is determined at compile time, and optimizes +1 to 11. To test the compiler’s intelligence, let’s change the assignment of age to runtime

    public void run() {
        final int age = (int) System.currentTimeMillis();
        Runnable runnable = new Runnable() {
            @Override
            public void run() { int myAge = age + 1; System.out.println(myAge); }}; }Copy the code

Look at the Demo$1 bytecode implementation

class DemoThe $1 implements Runnable {
    DemoThe $1(Demo var1, int var2) {
        this.this$0 = var1;
        this.val$age = var2;
    }

    public void run() {
        int var1 = this.val$age+ 1; System.out.println(var1); }}Copy the code

The compiler realizes that the compile-time value of age is indeterminate, so it implements it as a constructor parameter. Modern compilers are clever.

External class member variables

Change age to a member variable of Demo, without any modifiers, package level access level.

public class Demo {
    int age = 10;
    public void run() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() { int myAge = age + 1; System.out.println(myAge); age = 20; }}; }}Copy the code

Javac demo. Java, view the implementation within anonymous interior

class DemoThe $1 implements Runnable {
    DemoThe $1(Demo var1) {
        this.this$0 = var1;
    }

    public void run() {
        int var1 = this.this$0.age + 1;
        System.out.println(var1);
        this.this$0.age = 20; }}Copy the code

This time the compiler operates directly on age by reference to the external class, which is most efficient since age is the package access level. If you make age private, the compiler will generate two methods in the Demo class, one for reading age and the other for setting age.

Answer why local variables passed to anonymous inner classes need to be final?

As you can see from the above example, local variables don’t have to be final, but you can’t change external local variables in an anonymous inner class, because Java’s implementation of passing variables in an anonymous inner class is based on constructor passing parameters, which means that if you’re allowed to change values in an anonymous inner class, You are modifying a copy of an external local variable in an anonymous inner class, which ultimately does not have an effect on the external class because there are already two variables. This can be confusing for programmers, who think the changes will work when they don’t, so Java forbids modifying external local variables in anonymous inner classes.

How do I create an instance of an inner class, and how do I inherit an inner class?

Since an inner class object needs to hold a reference to an outer class object, it must have an outer class object first

Demo.DemoRunnable demoRunnable = new Demo().new DemoRunnable();
Copy the code

So how do you inherit an inner class, for example

    public class Demo2 extends Demo.DemoRunnable {
        public Demo2(Demo demo) {
            demo.super();
        }

        @Override
        public void run() { super.run(); }}Copy the code

A Demo object must be passed in the constructor, and demo.super() also needs to be called; Look at an example

public class DemoKata {
    public static void main(String[] args) {
        Demo2 demo2 = new DemoKata().new Demo2(new Demo());
    }

    public class Demo2 extends Demo.DemoRunnable {
        public Demo2(Demo demo) {
            demo.super();
        }

        @Override
        public void run() { super.run(); }}}Copy the code

Since Demo2 is also an inner class, you need to first new a DemoKata object. This question describes the scene may not be used much, generally not so to use, here to mention, you know there is such a thing on the line.

How are Lambda expressions implemented?

The introduction of Lambda expressions in Java8 simplifies our code somewhat and makes the structure of our code look more elegant. If you’re a technical person, have you ever wondered how Lambda is actually implemented in Java?

Let’s look at the simplest example

public class Animal {
    public void run(Runnable runnable) {
    }
}
Copy the code

The Animal class defines a run method that takes a Runnable object. Before Java8, we could pass in an anonymous inner class object

run(new Runnable() {
            @Override
            public void run() {}});Copy the code

Since Java 8, the compiler has been smart enough to suggest Lambda expressions instead. Since they can be replaced, are anonymous inner classes and Lambda expressions the same underlying implementation, or are Lambda expressions just syntactic sugar for anonymous inner classes? To answer this question, we still have to look for clues in the bytecode. The javac animal. Java command encodes a class as class, and the anonymous inner class creates an extra class. Will Lambda expressions also compile new classes? Let’s try it out.

    public void run(Runnable runnable) {
    }

    public void test() {
        run(() -> {});
    }
Copy the code

Javac animal.java, found no extra classes generated!! We continue to look at the bytecode implementation of animal. class using javap -verbose animal. class, focusing on the test method

  public void test(a); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokedynamic#2, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
         6: invokevirtual #3 // Method run:(Ljava/lang/Runnable;) V
         9: return

SourceFile: "Demo.java"
InnerClasses:
     public static final #34= #33 of #37; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #18 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:
      #19 ()V
      #20 invokestatic com/company/inner/Demo.lambda$test$0:()V
      #19 ()V

Copy the code

You found an invokedynamic # 20 directive in the bytecode of the test method, a new directive introduced in java7, where #2 points to

#2 = InvokeDynamic #0:#21 // #0:run:()Ljava/lang/Runnable;
Copy the code

And 0 (BootstrapMethods method in the table first, Java/lang/invoke/LambdaMetafactory metafactory method is invoked.

BootstrapMethods:
  0: #18 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:
      #19 ()V
      #20 invokestatic com/company/inner/Demo.lambda$test$0:()V
      #19 ()V
Copy the code

There we saw the com/company/inner/Demo. Lambda $$0 test such a thing, looks like we have some similar to the name of the anonymous inner class, and has a lambda in the middle, is probably the generated class we are looking for. To verify our idea, we can print out the actual class name of a Lambda object with the following code.

    public void run(Runnable runnable) {
        System.out.println(runnable.getClass().getCanonicalName());
    }

    public void test() {
        run(() -> {});
    }
Copy the code

Print the class name of Runnable as follows

com.company.inner.Demo$$LambdaThe $1/ 764977973Copy the code

The above speculation is not completely consistent with us, we continue to find other clues, now that we have seen LambdaMetafactory. Metafactory this class is called, might as well continue to follow up to see it

    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

Within the new a InnerClassLambdaMetafactory object. The name looks suspicious. Follow up

public InnerClassLambdaMetafactory(...)
            throws LambdaConversionException {
        //....
        lambdaClassName = targetClass.getName().replace('. '.'/') + "$$Lambda$" + counter.incrementAndGet();
        cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
       //....
    }
Copy the code

We’ve omitted a lot of code, but we’ll focus on the string lambdaClassName. We can see that the concatenation is basically the same as the Lambda class name we printed above. Lambda uses Asm bytecode technology to generate class files at run time. I feel like that’s about it, and then maybe it’s a little too detailed. -. –

Summary of Lambda implementation

So Lambda expressions are not syntactic sugar for anonymous inner classes, they are implemented at run time using ASM generated class files based on the InvokeDynamic directive.

Write in the last

This is probably the longest technical article I have written so far. In the process of writing, I have deepened my understanding of knowledge points and overturned many previous misunderstandings. I will continue to write technical articles. I really like the slogan “Slogan”, which Mr. Hu Shi said. What is the fear of infinite truth, into an inch an inch of joy!