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?
3.1. Since they have no name, we can't extend them. For the same reason, anonymous classes cannot have explicitly declared constructors.
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.
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.
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.
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)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With