Reputation: 9020
Do local inner classes (which I believe includes anonymous classes) maintain a copy of all variables defined in the scope of the method that these local inner classes are defined in?
It would be because if the local inner classes were using the local variables and the method went out of scope while the inner class object was still alive, the object would be using something which no longer exists.
But is this statement true? Is it mentioned somewhere in official docs?
This is a follow-up question of this answer. I left a comment there but did not get a response, and I figured this is a different question as well, so asking here.
Upvotes: 1
Views: 621
Reputation: 272657
There are a couple of things going on here:
So to revisit your title question:
Do local inner classes maintain a copy of all local variables in the scope that they are defined in?
The answer is that the JLS says nothing about it, and in a practical compiler implementation it will probably only be a copy of the final/effectively-final variables.
Upvotes: 1
Reputation: 109597
Just for clarity
An inner (non-static) class object has access to the outer classes this. On object has some implicit fields, this
, Outer.this, OuterOuter.this as far as the nesting goes.
class Outer {
int outerField;
class Inner {
String innerField;
... innerField ... outerField
... this.innerField
... Outer.this
}
... new Inner();
}
Outer outer = new Outer();
Inner inner = outer.new Inner();
Advantage: Inner classes allow to create a container class providing items, and every item has access to the container using the outer-this field.
Disadvantage: if one serializes such an inner class object to file, it also serializes the outer object, so the behavior can be weird.
(As such the answers should be obvious.)
Upvotes: 1
Reputation: 23870
Do local inner classes maintain a copy of all local variables in the scope that they are defined in?
"Copy" is an ambiguous term.
But some sort of copying has to take place in order to deal with the two scopes that will each go their own way after instantiation.
First of all, local inner classes can only refer to variables of enclosing scopes if those are "final or effectively final", as javac
calls it.
Trying to use a variable whose value changes after a local inner class declaration will produce an error message like:
Test.java:13: error: local variables referenced from an inner class must be final or effectively final
System.out.println(i);
^
Since we therefore only have final variables, primitive values can be copied without any headache.
As for objects, since they are actually pointers in Java, we only copy the pointer and not the entire object, just like in any other object variable assignment.
Doing so creates an additional "reference" to the object, which will prevent the garbage collector from cleaning it up once the original variable goes out of scope, as long as our inner class is still running.
Is it mentioned somewhere in official docs?
I don't believe so. The only thing I could find was in §8.1.3 of the Java Language Specification, which is pretty vague:
When an inner class (whose declaration does not occur in a static context) refers to an instance variable that is a member of a lexically enclosing class, the variable of the corresponding lexically enclosing instance is used.
(And I'm not sure whether a declaration inside a static
method meets the definition of "static context", or not.)
You can, however, inspect how javac
deals with this, by disassembling a .class
file using javap -c -private ...
.
(Assuming Java 8 from here on)
Given the following code:
class Test
{
public static void main(String[] args)
{
int i = 42;
String s = "abc";
Object o = new Object();
java.io.InputStream in = null;
Runnable r = new Runnable()
{
public void run()
{
System.out.println(i);
System.out.println(s);
System.out.println(o);
System.out.println(in);
}
};
}
}
So we have an int
, a String
, an Object
and an InputStream
in the outer scope, referenced to from the inner scope.
A call to javac Main.java
creates two files: Test.class
and Test$1.class
.
Test.class
isn't too interesting, but Test$1.class
reveals how exactly those variables are stored and accessed later:
bash$ javap -c -private 'Test$1.class'
Compiled from "Test.java"
final class Test$1 implements java.lang.Runnable {
final int val$i;
final java.lang.String val$s;
final java.lang.Object val$o;
final java.io.InputStream val$in;
Test$1(int, java.lang.String, java.lang.Object, java.io.InputStream);
Code:
0: aload_0
1: iload_1
2: putfield #1 // Field val$i:I
5: aload_0
6: aload_2
7: putfield #2 // Field val$s:Ljava/lang/String;
10: aload_0
11: aload_3
12: putfield #3 // Field val$o:Ljava/lang/Object;
15: aload_0
16: aload 4
18: putfield #4 // Field val$in:Ljava/io/InputStream;
21: aload_0
22: invokespecial #5 // Method java/lang/Object."<init>":()V
25: return
public void run();
Code:
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #1 // Field val$i:I
7: invokevirtual #7 // Method java/io/PrintStream.println:(I)V
10: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_0
14: getfield #2 // Field val$s:Ljava/lang/String;
17: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
23: aload_0
24: getfield #3 // Field val$o:Ljava/lang/Object;
27: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
30: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_0
34: getfield #4 // Field val$in:Ljava/io/InputStream;
37: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
40: return
}
So basically javac
just creates a final
field and a corresponding constructor parameter in the inner class for each referenced variable of the outer scope.
I don't know how other compilers do this, but
Upvotes: 2
Reputation: 425198
No, local inner classes don't "keep a copy", but they can reference final
or, since java-8, effectively final local variables in the method.
More insidiously, they keep a reference to the instance, which can cause memory leaks.
Upvotes: 4