Search code examples
javaconstructorjavacanonymous-classjavap

Java: initialization and costructor of anonymous classes


I would like to understand a strange behavior I faced dealing with anonymous classes.

I have a class that calls a protected method inside its constructor (I know, poor design but that's another story...)

public class A {
  public A() {
    init();
  }
  protected void init() {}
}

then I have another class that extends A and overrides init().

public class B extends A {
  int value;
  public B(int i) {
    value = i;
  }
  protected void init() {
    System.out.println("value="+value);
  }
}

If I code

B b = new B(10);

I get

> value=0

and that's expected because the constructor of the super class is invoked before the B ctor and then value is still.

But when using an anonymous class like this

class C {
  public static void main (String[] args) {
    final int avalue = Integer.parsetInt(args[0]);
    A a = new A() {
      void init() { System.out.println("value="+avalue); }
    }
  }
}

I would expect to get value=0 because this should be more or less equal to class B: the compiler automatically creates a new class C$1 that extends A and creates instance variables to store local variables referenced in the methods of the anonymous class, simulating a closure etc...

But when you run this, I got

> java -cp . C 42
> value=42

Initially I was thinking that this was due to the fact that I was using java 8, and maybe, when introducing lamdbas, they changed the way anonymous classes are implemented under the hood (you no longer need for final), but I tried with java 7 also and got the same result...

Actually, looking at the byte code with javap, I can see that B is

> javap -c B
Compiled from "B.java"
public class B extends A {
  int value;

  public B(int);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method A."<init>":()V
       4: aload_0
       5: iload_1
       6: putfield      #2                  // Field value:I
       9: return
...

while for C$1:

> javap -c C\$1
Compiled from "C.java"
final class C$1 extends A {
  final int val$v;

  C$1(int);
    Code:
       0: aload_0
       1: iload_1
       2: putfield      #1                  // Field val$v:I
       5: aload_0
       6: invokespecial #2                  // Method A."<init>":()V
       9: return
....

Could someone tell me why this difference? Is there a way to replicate the behavior of the anonymous class using "normal" classes?

EDIT: to clarify the question: why does the initialization of the anonymous classes break the rules for initializing of any other class (where super constructor is invoked before setting any other variable)? Or, is there a way to set instance variable in B class before inovking super constructor?


Solution

  • This question applies to all inner classes, not just anon classes. (Anon classes are inner classes)

    JLS does not dictates how an inner class body accesses outer local variable; it only specifies that the local variables are effectively final, and definitely assigned before the inner class body. Therefore, it stands to reason that the inner class must see the definitely assigned value of the local variable.

    JLS does not specify exactly how the inner class sees that value; it is up to the compiler to use whatever trick (that is possible on the bytecode level) to achieve that effect. In particular, this issue is completely unrelated to constructors (as far as the language is concerned).

    A similar issue is how an inner class accesses the outer instance. This is a bit more complicated, and it does have something to do with constructors. Nevertheless, JLS still does not dictate how it is achieved by the compiler; the section contains a comment that "... compiler can represent the immediately enclosing instance how ever it wishes. There is no need for the Java programming language to ... "


    From JMM point of view, this under-specification might be a problem; it is unclear how writes were done in relation to reads in inner class. It is reasonable to assume that, a write is done on a synthetic variable, which is before (in programming order) the new InnerClass() action; the inner class reads the synthetic variable to see the outer local variable or the enclosing instance.


    Is there a way to replicate the behavior of the anonymous class using "normal" classes?

    You may arrange the "normal" class as outer-inner class

    public class B0
    {
        int value;
        public B0(int i){ value=i; }
    
        public class B extends A
        {
            protected void init()
            {
                System.out.println("value="+value);
            }
        }
    }
    

    It will be used like this, which prints 10

        new B0(10).new B();
    

    A convenience factory method can be added to hide the syntax ugliness

        newB(10);
    
    public static B0.B newB(int arg){ return new B0(arg).new B(); }
    

    So we split our class into 2 parts; the outer part is executed even before the super constructor. This is useful in some cases. (another example)


    ( inner anonymous access local variable enclosing instance effective final super constructor)