Java: initialization and costructor of anonymous classes

I would like to understand the strange behavior that I came across with anonymous classes.

I have a class that calls a protected method inside its constructor (I know the bad design, but this is 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 the code

 B b = new B(10); 

I get

 > value=0 

and this was expected because the superclass constructor is called before 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 it should be more or less equal to class B : the compiler automatically creates a new class C$1 , which extends A and creates instance variables to store local variables that are referenced in methods of an anonymous class that simulate closure etc.

But when you run this, I got

 > java -cp . C 42 > value=42 

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

Actually, looking at the byte code with javap , I 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 ... 

and 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 .... 

Can someone tell me why this difference is? Is there a way to reproduce the behavior of an anonymous class using "normal" classes?

EDIT: to clarify the question: why does initializing anonymous classes violate the rules for initializing any other class (where the super constructor is called before setting any other variable)? Or is there a way to set the instance variable in class B before running the superstructor?

+5
source share
4 answers

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

JLS does not define how the internal body of a class accesses an external local variable; it only indicates that local variables are actually final and are definitely assigned in front of the inner body of the class. Therefore, it is reasonable that the inner class should see the definitely assigned value of the local variable.

JLS does not indicate exactly how the inner class sees this value; To achieve this effect, the compiler must use any trick (which is possible at the bytecode level). In particular, this question is completely unrelated to the designers (as far as language is concerned).

A similar problem is how the inner class accesses the outer instance. It's a little trickier, and it has something regarding constructors. However, JLS still does not dictate how this is achieved by the compiler; the section contains a comment in which "... the compiler can instantly include an instance as it wishes. It is not necessary for the Java programming language ..."


From JMM's point of view, this underestimation can be a problem; it is unclear how the notes were made regarding reading in the inner class. It is reasonable to assume that recording is performed using a synthetic variable that precedes (in programming order) the action of new InnerClass() ; the inner class reads the synthetic variable to see the external local variable or the enclosing instance.


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

You can arrange the "normal" class as an 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 in a way that prints 10

  new B0(10).new B(); 

A convenient factory method can be added to hide syntactic ugliness

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

So, we divided our class into 2 parts; the outer part is carried out before the superconstructor. This is useful in some cases. (one more example)


(internal anonymous access to the local variable containing the instance, effective final super-constructor)

+3
source

Your anonymous class instance behaves differently than your first code snippet, because you are using a local variable whose value is initialized before an instance of the anonymous class is instantiated.

You can get similar behavior with the first fragment with an instance of an anonymous class if you use an instance variable in an anonymous class:

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

Will open

 value=0 

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

+2
source

Capturing a variable in anonymous classes allows you to break the rules of normal constructors (calling a super-constructor should be the first statement), since this law is applied only by the compiler. The JVM allows you to run any bytecode before calling the super constructor, which is used by the compiler (it violates its own rules!) For anonymous classes.

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

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

The restriction is actually quite 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 need to do the computeComplexString calculation twice, because there is no way to either pass it to the superstructor and save it in the instance variable.

+2
source

Two examples are not related.

In example B:

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

the printed value is the value field of instance B.

In an anonymous example:

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

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

0
source

All Articles