Solace
Solace

Reputation: 9020

Do local inner classes maintain a copy of all local variables in the scope that they are defined in?

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

Answers (4)

Oliver Charlesworth
Oliver Charlesworth

Reputation: 272657

There are a couple of things going on here:

  • The JLS (Java Language Standard) is concerned with semantics. Semantically, there is no copy going on (or rather, the effect of a copy would be indistinguishable). So it says nothing about it.
  • The compiler is concerned with implementation. Creating an under-the-hood copy of variables is a very easy way to implement the semantics of local-variable capture (given non-mutation).
  • The authors of the JLS were of course aware of the above point, so almost certainly curated the semantics (in particular, the constraints around final/effectively-final) accordingly.

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

Joop Eggen
Joop Eggen

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

Siguza
Siguza

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

  • I haven't found a specification for this.
  • You can inspect how each of them does it, using your favourite Java disassembler.

Upvotes: 2

Bohemian
Bohemian

Reputation: 425198

No, local inner classes don't "keep a copy", but they can reference final or, since , effectively final local variables in the method.

More insidiously, they keep a reference to the instance, which can cause memory leaks.

Upvotes: 4

Related Questions