Ae pessoas,
recebi esse artigo pela lista do java specialist, achei muito interessante, era uma coisa que eu não sabia e que pode gerar um daqueles erros quase impossíveis de serem identificados.
Façam bom proveito.
Java Field Initialisation The idea is to have two classes, an abstract superclass and a subclass implementation. The subclass' constructor calls the superclass constructor, which in its turn calls an abstract method implemented by the subclass. The method implementation sets a (subclass) member variable - and this is where the fun begins.I have often seen code like this:
The writer of MyClass was being overcautious, by initialising fields that are actually being set in the constructor anyway. "Oh, but it does no harm." Really? Before we look at Remco's example, let us decompile the class and see what the compiler did with the field initialisers:public class MyClass { private boolean b = true; // unnecessary initialisation private int i = 42; // unnecessary initialisation public MyClass(boolean b, int i) { this.b = b; this.i = i; } }If we look carefully, we see that the field initialisers get copied into the constructor as part of compilation. The steps "b = true" and "i = 42" are thus of no use at all. I find this interesting. All the initialising code and the initialiser blocks, are copied into each of the constructors.public class MyClass { public MyClass(boolean flag, int j) { b = true; i = 42; b = flag; i = j; } private boolean b; private int i; }Another quick example:
becomes the compiled class:public class MyClass2 { { System.out.println("Goodbye"); } public MyClass2() { } public MyClass2(boolean b) { } public MyClass2(boolean b, String s) { } private int i = 4; { System.out.println("Hello"); } }Now, let us look at the classes that Remco sent me:public class MyClass2 { public MyClass2() { System.out.println("Goodbye"); i = 4; System.out.println("Hello"); } public MyClass2(boolean flag) { System.out.println("Goodbye"); i = 4; System.out.println("Hello"); } public MyClass2(boolean flag, String s) { System.out.println("Goodbye"); i = 4; System.out.println("Hello"); } private int i; }and its subclasspublic abstract class A { public A(int i) { build(i); } protected abstract void build(int i); }The resultant output is:public class B extends A { private int size = 0; public B(int size) { super(size); } protected void build(int size) { this.size = size; } public int size() { return size; } public static void main(String[] args) { B test = new B(1); System.out.println("Size: " + test.size()); } }Size: 0
Correct, but it is easy to think that the output should rather be "Size: 1", but it is "Size: 0"! You can prevent this by not explicitly setting the B.size field to 0 in the declaration. In other words, declaring private int size; provides the expected answer ('Size: 1').According to the JVM Language Specification, this is expected behaviour. A superclass is initialized before the member variables of a subclass. Explicitly setting the member variable to 0 therefore takes place after the super constructor called the build method (note that you can set the size to anything you want, not necessarily 0). When you leave out the explicit '= 0', the variable is of course still (implicitly) initialized to 0. However, this implicit default initialization ('preparation') is performed before the superclass constructor.
It makes sense, when we consider that the initialisation code (size=1) is moved to the start of each constructor.
final: The confusion could have been avoided by changing the design of the application. I usually try to make all fields final. This would have highlighted the problem in the code from the start.
Kind regards
Heinz