preface

Let’s start with a very simple question:

public class AnonymousDemo1
{
    public static void main(String args[])
    {
        new AnonymousDemo1().play(a);
    }

    private void play(a)
    {
        Dog dog = new Dog(a);
        Runnable runnable = new Runnable(a)
        {
            public void run(a)
            {
                while(dog.getAge()"100)
                {
                    // Birthday, age + 1
                    dog.happyBirthday(a);
                    // Print the age
                    System.out.println(dog.getAge());
                }
            }
        };
        new Thread(runnable).start(a);
        
        // do other thing below when dog's age is increasing
        / /...
    }
}
Copy the code

public class Dog
{
    private int age;

    public int getAge(a)
    {
        return age;
    }

    public void setAge(int age)
    {
        this.age = age;
    }

    public void happyBirthday(a)
    {
        this.age+ +;
    }
}
Copy the code

The function of this program is very simple, is to start a thread, to simulate a dog birthday a process.

However, this code does not compile, why, take a closer look!

.

.

.

.

Can you see that? In the play() method, the final modifier should be added to the dog variable, otherwise the compiler will prompt:

Cannot refer to a non-final variable dog inside an inner class defined in a different method

After adding final, the compilation passes and the program runs normally.

But why do we have to add final here?

When learning Java, we’ve all heard this phrase (or something similar) :

Anonymous inner classes from
External closure environmentthe
Free variablesIt must be final

I was confused. What is a closure? What is a free variable? Finally, I don’t want to solve very well. Anyway, in the future, I will add a final in this case.

Obviously, this kind of attitude towards knowledge is not good, we must “know what it is and why it is”. Recently, I have done some research on this problem, hoping to share it with you in a more understandable language.

We learn frameworks, look at the source code, learn design patterns, learn concurrent programming, learn cache, and even understand the architecture of large websites. We can look back at some very simple Java code, but we find that there are still several corners that we do not fully see through.

The truth about anonymous inner classes

If you can’t compile without final, add final and see the decompilation of the class file.

Anonymousdemo1. class and AnonymousDemo1$1.class, where the class with the dollar sign $is the anonymous inner class in our code. Next, decompile using JD-GUI and look at the anonymous inner class:

class AnonymousDemo1$1
  implements Runnable
{
  AnonymousDemo1$1(AnonymousDemo1 paramAnonymousDemo1. Dog paramDog) {}
  
  public void run(a)
  {
    while (this.val$dog.getAge(a) < 100)
    {
      this.val$dog.happyBirthday(a);
      
      System.out.println(this.val$dog.getAge());
    }
  }
}
Copy the code

This code doesn’t make sense:

  • First, the constructor passed in two variables, one of type AnonymousDemo1 and the other of type Dog, but the method body is empty.
  • Furthermore, the run method’s this.val$dog member variable is not defined in the class, and seems to have been lost in the decompilation process.

Since the jD-GUI decompilation does not fully show the compiled code, the only way to disassemble it is to use the javap command, which is executed on the command line:

javap -c AnonymousDemo1$1.class
Copy the code

After executing the command, you can see some assembly instructions on the console, focusing on the constructor of the inner class:

com.bridgeforyou.anonymous.AnonymousDemo1$1(com.bridgeforyou.anonymous.Anonymo usDemo1, com.bridgeforyou.anonymous.Dog);  Code: 0: aload_0 1: aload_1 2: putfield #14 // Field this$0:Lcom/bridgeforyou/an onymous/AnonymousDemo1; 5: aload_0 6: aload_2 7: putfield #16 // Field val$dog:Lcom/bridgeforyou/a nonymous/Dog; 10: aload_0 11: invokespecial #18 // Method java/lang/Object."<init>": ()V 14: returnCopy the code

The focus of this instruction is on the second putfield instruction. With the comment, we can see that the constructor function assigns the dog variable passed to another variable. Now we can manually fill in the decomcompiled code that was left out by the above information:

class AnonymousDemo1$1
  implements Runnable
{
  private Dog val$dog;
  private AnonymousDemo1 myAnonymousDemo1;
  
  AnonymousDemo1$1(AnonymousDemo1 paramAnonymousDemo1. Dog paramDog) {
	this.myAnonymousDemo1 = paramAnonymousDemo1;
	this.val$dog = paramDog;
  }
  
  public void run(a)
  {
    while (this.val$dog.getAge(a) < 100)
    {
      this.val$dog.happyBirthday(a);
      
      System.out.println(this.val$dog.getAge());
    }
  }
}
Copy the code

The AnonymousDemo1 external class simply passes the dog variable to the AnonymousDemo1$1 constructor and creates an instance of the inner class, like this:

public class AnonymousDemo1
{
  public static void main(String[] args)
  {
    new AnonymousDemo1().play(a);
  }
  
  private void play(a)
  {
    final Dog dog = new Dog(a);
    Runnable runnable = new AnonymousDemo1$1(this. dog);
    new Thread(runnable).start(a);
  }
}
Copy the code

For Java assembly instructions, see
Java bytecode instruction listings

So far we have seen the full extent of anonymous inner classes. Java simply copies a variable from an outer class to another variable from an inner class.

Why do WE need final

As I mentioned in this article, Java variables are not objects. In this case, both the val$dog variable of the inner class and the dog variable of the outer class are just variables that store the address of the instance of the object. Because of the copying, These two variables refer to the same dog (object).

So why does Java require that dog of an external class be final?

A variable modified by final:

  • If the variable is a primitive data type, its value cannot be changed;
  • If the variable is a reference to an object, the address to which it points cannot be changed.

Wikipedia is very clear about final
final (Java) – Wikipedia

So, in this example, if we don’t add final, I can add dog = new dog () at the end of the code; Like this:

// ...
new Thread(runnable).start(a);

// do other thing below when dog's age is increasing
dog = new Dog(a);
Copy the code

This way, the outer dog variable points to another dog, while the inner val$dog variable points to the original dog, like this:

The result is that variables in the inner class are out of sync with variables in the outer environment, pointing to different objects.

Therefore, the compiler requires us to make the dog variable final to prevent this kind of unsynchronization.

Why copy it

Now we know that due to the action of a copy, the internal and external variables cannot be synchronized in real time. One of the variables can be modified, and neither of the other variables can be modified in real time. Therefore, we need to add the final restriction that the variables cannot be modified.

So why copy it? Wouldn’t it be so much trouble if we didn’t copy it?

Consider the runtime data area of the Java VIRTUAL machine. The dog variable is inside the method, so dog is on the vm stack, which means that the variable cannot be shared and the anonymous inner class cannot be accessed directly, so it can only be passed to the anonymous inner class by value.

So are there situations where you don’t need a copy? Yes, please go ahead.

Does it have to be final

Now that we understand the reason behind adding final, I lift out the dog variable, which was inside the function, and “promote” it to a member variable of the class, like this:

public class AnonymousDemo2
{
    private Dog dog = new Dog(a);

    public static void main(String args[])
    {
        new AnonymousDemo2().play(a);
    }

    private void play(a)
    {
        Runnable runnable = new Runnable(a)
        {
            public void run(a)
            {
                while (dog.getAge(a) < 100)
                {
                    // Birthday, age + 1
                    dog.happyBirthday(a);
                    // Print the age
                    System.out.println(dog.getAge());
                }
            }
        };
        new Thread(runnable).start(a);

        // do other thing below when dog's age is increasing
        / /...
    }
}
Copy the code

Dog becomes a member variable, corresponding to the heap location in the virtual machine, and we can get this variable from anywhere in the class just by passing this.dog. Therefore, when creating the inner class, there is no need to copy or even pass the dog to the inner class.

By decompiling, you can see that this time, the constructor of the inner class takes only one argument:

class AnonymousDemo2$1
  implements Runnable
{
  AnonymousDemo2$1(AnonymousDemo2 paramAnonymousDemo2) {}
  
  public void run(a)
  {
    while (AnonymousDemo2.access$0(this.this$0).getAge(a) < 100)
    {
      AnonymousDemo2.access$0(this.this$0).happyBirthday(a);
      
      System.out.println(AnonymousDemo2.access$0(this.this$0).getAge());
    }
  }
}
Copy the code

In the run method, the AnonymousDemo2 class gets the dog object directly. Using the javap disassembly instruction, we can also restore the code:

class AnonymousDemo2$1
  implements Runnable
{
  private AnonymousDemo2 myAnonymousDemo2;
  
  AnonymousDemo2$1(AnonymousDemo2 paramAnonymousDemo2) {
	this.myAnonymousDemo2 = paramAnonymousDemo2;
  }
  
  public void run(a)
  {
    while (this.myAnonymousDemo2.getAge(a) < 100)
    {
      this.myAnonymousDemo2.happyBirthday(a);
      
      System.out.println(this.myAnonymousDemo2.getAge());
    }
  }
}
Copy the code

Compared to Demo1, the dog variable in Demo2 has the advantage of being “naturally synchronized”, so no copy is required, and the compiler does not require final.

Look back at that classic quote

The free variables of an anonymous inner class from an external closure must be final.

First of all, what are the free variables?

A function of the “free variable” is neither a function parameter also than the local variables of the function, this variable in a function in the runtime context, like the dog in the demo, could run for the first time, the dog points to is the age of 10 dogs, but in the second run, is the age of 11 dog.

And then, what is the external closure environment?

If the external environment holds the free variable used by the inner function, it will form a “closure” for the inner function. In Demo1, the external play method holds the dog variable in the inner class, thus forming a closure:

【 figure 】

Demo2 is also a closure, and if so, it should be more like this:

Anonymous inner class free variables from the external closure environment must be final, unless the free variable comes from a member variable of the class.

Compare JavaScript closures

As you can see from the above, if Java anonymous inner classes are a closure, this is a somewhat “incomplete” closure because it requires the external environment to hold free variables that must be final.

For other languages, such as C# and JavaScript, there is no such requirement, and internal and external variables can be automatically synchronized, such as this JavaScript code (directly press F12 to open the browser debug window, paste the code into the Console TAB, and press enter) :

function fn(a) {
    var myVar = 42;
    var lambdaFun = (a) = > myVar;
    console.log(lambdaFun()); // print 42
    myVar++;
    console.log(lambdaFun()); // print 43
}
fn(a);
Copy the code

This code creates a function using a lambda expression (also provided by Java8, described below) that directly returns the external variable myVar. After creating the function, modify myVar, and you can see that the variables inside the function are also changed synchronously.

This closure, it should be said, is the more “normal” and “complete” closure.

Changes since Java8

In JDK1.8, lambda expressions are also provided, allowing us to simplify anonymous inner classes, such as this code:

int answer = 42;
Thread t = new Thread(new Runnable(a) {
    public void run(a) {
        System.out.println("The answer is: " + answer);
   }
});
Copy the code

After lambda expression modification, it looks like this:

int answer = 42;
Thread t = new Thread(
    (a) -> System.out.println("The answer is: " + answer)
);
Copy the code

It is important to note that starting with JDK1.8, the compiler does not require that a free variable be declared final. If the variable does not change in subsequent use, it can be compiled, which Java calls “effectively final.”

The above example is “effectively final”, because the answer variable does not change after the definition, and the following example will not compile:

int answer = 42;
answer + +; // don't do this !
Thread t = new Thread(
   (a) -> System.out.println("The answer is: " + answer)
);
Copy the code

trivia

When researching this problem, I referred to this problem in StackOverflow: Cannot refer to a non-final variable inside an inner class defined ina different method

One of the most liked and accepted answers was this:

When the main() method returns, local variables (such as lastPrice and price) will be cleaned up from the stack, so they won’t exist anymore after main() returns.


But the anonymous class object references these variables. Things would go horribly wrong if the anonymous class object tries to access the variables after they have been cleaned up.


By making lastPrice and price final, they are not really variables anymore, but constants. The compiler can then just replace the use of lastPrice and price in the anonymous class with the values of the constants (at compile time, of course), and you won’t have the problem with accessing non-existent variables anymore.

Since external variables are destroyed at the end of the method, declare them as final constants so that the inner class can use them even if the external class’s variables are destroyed.

Such a simple, groundless explanation got so many likes that when someone pointed it out in the comments section, the respondent added another line to his answer:

edit
See the comments below – the following is not a correct explanation, as KeeperOfTheSoul points out.

We can see that when we look at a problem, we can not only explain it from the surface. To explain a problem, we must make clear the principle behind it.

Refer to the content

  • Cannot refer to a non-final variable inside an inner class defined in a different method
  • Why a non-final “local” variable cannot be used inside an inner class, and instead a non-final field of the enclosing class can?
  • Captured variable in a loop in C#
  • Java 8 Lambda Limitations: Closures – DZone Java
  • Difference between final and effectively final
  • final (Java) – Wikipedia
  • Why is Java final when referencing an anonymous inner class parameter?
  • Java bytecode instruction listings
  • What are Free and Bound variables?