R
Roedy Green
After Thomas surprised me with the enum decompilation, I thought I
would have a look at a static init decompilation to see I what I
thought was true was indeed true.
Here is my little test program:
// experiment to see order of initialisation
public class Init2
{
static {
// define m multiple times
m = 1;
}
static final int inlined = 2;
static final int notInlined = Math.min( 3 , 4 );
static int m = 5;
static
{
m = inlined + 6; // effectively 8
m = notInlined + 7; // effectively 10
}
}
DJ compiles it like this:
public class Init2
{
public Init2()
{
}
static final int inlined = 2;
static final int notInlined;
static int m = 1;
static
{
notInlined = Math.min(3, 4);
m = 5;
m = 8;
m = notInlined + 7;
}
Javap decompiles it like this:
javap -c Init2
Compiled from "Init2.java"
public class Init2 extends java.lang.Object{
static final int inlined;
static final int notInlined;
static int m;
public Init2();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>"
)V
4: return
static {};
Code:
0: iconst_1
1: putstatic #2; //Field m:I
4: iconst_3
5: iconst_4
6: invokestatic #3; //Method java/lang/Math.min
II)I
9: putstatic #4; //Field notInlined:I
12: iconst_5
13: putstatic #2; //Field m:I
16: bipush 8
18: putstatic #2; //Field m:I
21: getstatic #4; //Field notInlined:I
24: bipush 7
26: iadd
27: putstatic #2; //Field m:I
30: return
}
So what do we discover? Probably nothing new to the old timers.
1. static final int inlined = 2; gets inlined. It is treated like a
literal. The line m = inlined + 6; is effectively turned to m = 2+ 6;
which is collapsed further to m = 8;.
This is why you have to recompile the classes that use constants in
some other class when that base class changes the constant values. The
dependent code has to be re-inlined. The original source of the
constant value is lost in the class file.
2. The initialisations are done in strict order, whether they are
attached to a single line or are part of a static block. You might
reasonably have expected all the one liners to have been done first,
but that is not so.
This is why you have to be careful with field-ordering IDEs like
Eclipse. Changing the field order can change the dependency order
during initialization. There is no clever spreadsheet-like discovery
of dependencies. Initialisation proceeds mindlessly top to bottom.
3. You are allowed to initialise variables not defined yet in static
blocks, but you are not allowed to refer to variables not initialised
yet. This is because the definition and initialisation, though mixed
together in Java source, are separate in the class file. Note the m=1.
4. JavaC is not doing even the most rudimentary optimisation of
discarding a save to a variable immediately followed by a save of a
different value to that same variable.
would have a look at a static init decompilation to see I what I
thought was true was indeed true.
Here is my little test program:
// experiment to see order of initialisation
public class Init2
{
static {
// define m multiple times
m = 1;
}
static final int inlined = 2;
static final int notInlined = Math.min( 3 , 4 );
static int m = 5;
static
{
m = inlined + 6; // effectively 8
m = notInlined + 7; // effectively 10
}
}
DJ compiles it like this:
public class Init2
{
public Init2()
{
}
static final int inlined = 2;
static final int notInlined;
static int m = 1;
static
{
notInlined = Math.min(3, 4);
m = 5;
m = 8;
m = notInlined + 7;
}
Javap decompiles it like this:
javap -c Init2
Compiled from "Init2.java"
public class Init2 extends java.lang.Object{
static final int inlined;
static final int notInlined;
static int m;
public Init2();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>"
4: return
static {};
Code:
0: iconst_1
1: putstatic #2; //Field m:I
4: iconst_3
5: iconst_4
6: invokestatic #3; //Method java/lang/Math.min
9: putstatic #4; //Field notInlined:I
12: iconst_5
13: putstatic #2; //Field m:I
16: bipush 8
18: putstatic #2; //Field m:I
21: getstatic #4; //Field notInlined:I
24: bipush 7
26: iadd
27: putstatic #2; //Field m:I
30: return
}
So what do we discover? Probably nothing new to the old timers.
1. static final int inlined = 2; gets inlined. It is treated like a
literal. The line m = inlined + 6; is effectively turned to m = 2+ 6;
which is collapsed further to m = 8;.
This is why you have to recompile the classes that use constants in
some other class when that base class changes the constant values. The
dependent code has to be re-inlined. The original source of the
constant value is lost in the class file.
2. The initialisations are done in strict order, whether they are
attached to a single line or are part of a static block. You might
reasonably have expected all the one liners to have been done first,
but that is not so.
This is why you have to be careful with field-ordering IDEs like
Eclipse. Changing the field order can change the dependency order
during initialization. There is no clever spreadsheet-like discovery
of dependencies. Initialisation proceeds mindlessly top to bottom.
3. You are allowed to initialise variables not defined yet in static
blocks, but you are not allowed to refer to variables not initialised
yet. This is because the definition and initialisation, though mixed
together in Java source, are separate in the class file. Note the m=1.
4. JavaC is not doing even the most rudimentary optimisation of
discarding a save to a variable immediately followed by a save of a
different value to that same variable.