Why do external variables used by Java anonymous inner classes have to be final?

Problem Description

Insert image description here
Insert image description here

Effectively final

  • New feature of Java 1.8, for alocal variable or method parameter,if its value is in If it is never changed after initialization, then the variable is effectively final (actually final). In this case, there is no need to add the final keyword modification.

Insert image description here
Insert image description here

The inner class will hold a reference to the outer class object

  • Non-static inner classes hold references to outer classes;
    • Hold in the form of member variables automatically generated by the compiler;
    • is passed in through the constructor automatically generated by the compiler.
  • Static inner class, does not hold a reference to the outer class.

inner class

A non-static inner class will hold a reference to an outer class object through an automatically generated constructor:

Insert image description here
Insert image description here

Even if you add a non-default constructor to an inner class, the compiler will still automatically add a parameter of the outer class object to the constructor:

Insert image description here

static inner class

The automatically generated constructor of a static inner class will not hold a reference to the outer class:

Insert image description here

anonymous inner class

Anonymous inner classes also hold a reference to an outer class object through the constructor:

Insert image description here
Insert image description here

The anonymous inner class will pass the captured local variables in its constructor:

Insert image description here
Insert image description here
Insert image description here

The purpose of adding final to the external variables captured by the anonymous inner class is: Ensure that the copy reference captured by the anonymous inner class and the external local variables always point to the same object, that is, no one Their orientation can be modified.

Insert image description here

If the local variables of the external method are not addedfinal, it is possible that the pointing of the external local variables has changed, but the internal class does not know it. This may cause the object operated by the inner class and the external local variable to point to different objects, and bugs may occur.

Note: The local variables captured by anonymous inner classes plusfinal refer tolocal variables in the same method, If the variable in the external class is captured, there is no need to add final, because in this case, you can directly Obtain its member variables through the reference of the external class object.

Insert image description here
Insert image description here

Summarize

  • Anonymous inner classes hold references to outer classes;
    • Hold in the form of member variables automatically generated by the compiler;
    • is passed in through the constructor automatically generated by the compiler.
    • Anonymous inner class, access member variables and methods of the outer class through this reference.
  • When an anonymous inner class accesses an external local variable, it actually accesses a member variable of itself;
    • This member variable is automatically generated by the compiler;
    • This member variable is initialized by the constructor automatically generated by the compiler;
    • In order to ensure that this member variable and external local variables remain consistent at all times, both must befinal.

The fundamental reason is toensure the consistency of internal and external operations on the object of this local variable. Therefore, both copies are required to be immutable.

Anonymous inner classes in Kotlin

When an anonymous inner class in Kotlin does not have an external variable, it can ensure the consistency of the same local object accessed internally and externally even without using keyword modifications like final.

Insert image description here
Insert image description here
Insert image description here

Through the above bytecode analysis, we can draw the following conclusions:

  • Kotlin's anonymous inner class does not pass a reference to the entire outer class object in the constructor
  • But for the captured local variables, an immutable (val) guaranteed class object (ObjectRef) will be automatically generated and passed into the constructor.

The corresponding pseudocode structure 2 is as follows:

Insert image description here

What is ObjectRef:

Insert image description here

We see that it is ageneric class, which holds aelement internally. boxing class Athe problem of capturing local variables can be considered to be generated by Kotlin to solveGeneric member variables

Removed ObjectRef, Kotlin middle school ByteRef, ShortRef, IntRef, LongRef, FloatRef, DoubleRef, CharRef, BooleanRef.

So although the solution is different from Java's, the idea is essentially the same. Both internal and external operations on captured variables must be consistent, that is, these two must be ensured. Immutability of copies.

Insert image description here

Although the pointer of the wrapper class object is immutable, the contents contained in the wrapper class object can change the pointer, which is better than Java:

Insert image description here

All this is automatically implemented by the compiler for us, but for developers, the experience will be significantly different from Java: "Java needsfinal to capture, but Kotlin doesn’t need val capture”, but this is an illusion. In fact, Kotlin also needs it, but you can’t see it.

memory leak problem

The root cause of memory leaks is that a long-lived object is referenced by a short-lived object.

Insert image description here

  • Once the inner class object is referenced by a long-lived object, or its life cycle is too long, the outer class cannot be recycled by GC because they are on the same reference chain and are judged to be reachable according to the GC Root reachability analysis algorithm.
  • For example, , , < created through anonymous inner classes in Activity a i=4>etc. HandlerAsyncTaskResultReceiver

Solution:

  1. Do not use (anonymous) inner classes, place the definition of the class outside, pass parameters to the explicit constructor, and actively disconnect external access when the external class is destroyed (such asActivity.onDestroy()) Class reference (for example, many frameworks provide unbinding and deregistration APIs for users to call when the page is destroyed).
  2. In scenarios where inner classes must be used, static inner classes + weak references pointing to external class objects are used. (Since weak references will be recycled once discovered by GC scanning, there is no memory leak problem)
    Insert image description here

Guess you like

Origin blog.csdn.net/lyabc123456/article/details/134908586