Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 983
ugo Avatar asked Sep 08 '15 17:09

ugo


People also ask

Can you have a constructor in an anonymous class?

3.1. Since they have no name, we can't extend them. For the same reason, anonymous classes cannot have explicitly declared constructors.

Can we create constructor to anonymous class in Java?

A constructor should have the name same as the class. Since anonymous inner class has no name, an anonymous inner class cannot have an explicit constructor in Java. But, Java compiler internally creates a constructor for the anonymous class.

How do you initialize an anonymous class in Java?

Object = new Example() { public void display() { System. out. println("Anonymous class overrides the method display()."); } }; Here, an object of the anonymous class is created dynamically when we need to override the display() method.

How do you make an anonymous class constructor?

You can't. A constructor is declared using the class's name. An anonymous class, by definition, does not have a name. What you can do, however, is declare an instance initializer, just like you might do in a named class.


2 Answers

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)

like image 108
ZhongYu Avatar answered Nov 02 '22 05:11

ZhongYu


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.

like image 34
Eran Avatar answered Nov 02 '22 06:11

Eran