ugo
ugo

Reputation: 181

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?

Upvotes: 5

Views: 2467

Answers (4)

Clashsoft
Clashsoft

Reputation: 11882

The variable capture in anonymous classes is allowed to break the rules of normal constructors (super constructor call must be the first statement) because this law is only enforced by the compiler. The JVM allows any bytecode to be ran before invoking the super constructor, which is utilized by the compiler itself (it breaks it's own rules!) for anonymous classes.

You can mimic the behavior either with inner classes as shown in bayou.io's answer, or you can use an anonymous in a static B factory method:

public class B extends A
{
    public static B create(int value)
    {
        return new B() {
            void init() { System.out.println("value="+value);
        };
    }
}

The limitation is actually rather pointless and can be annoying in some situations:

class A
{
    private int len;

    public A(String s)
    {
        this.len = s.length();
    }
}

class B extends A
{
    private String complexString;

    public B(int i, double d)
    {
        super(computeComplexString(i, d));
        this.complexString = computeComplexString(i, d);
    }

    private static String computeComplexString(int i, double d)
    {
        // some code that takes a long time
    }
}

In this example, you have to do the computeComplexString computation twice, because there is no way to both pass it to the super constructor and store it in an instance variable.

Upvotes: 2

ZhongYu
ZhongYu

Reputation: 19702

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)

Upvotes: 3

Bohemian
Bohemian

Reputation: 425358

The two examples are not related.

In the B example:

protected void init() {
    System.out.println("value="+value);
}

the value being printed is the value field of the instance of B.

In the anonymous example:

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

the value being printed is the local variable avalue of the main() method.

Upvotes: 1

Eran
Eran

Reputation: 394126

Your anonymous class instance behaves differently than your first code snippet since you are using a local variable whose value is initialized before the anonymous class instance is created.

You can get a similar behavior to the first snippet with an anonymous class instance if you use an instance variable in the anonymous class :

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

This will print

value=0

since init() is executed by A's constructor before avalue is initialized.

Upvotes: 3

Related Questions