I couldn't find the exact reason about this in JLS, so I went through the byte code and found that the reason is that the compiler couldn't inline the value of i
in the second case, but is able to do it in the first case.
Here's the code:
final int x = 90;
System.out.println(x);
final int i;
i = 90;
System.out.println(i);
The compiled byte code looks like:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: bipush 90
5: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
8: bipush 90
10: istore_2
11: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
14: iload_2
15: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
18: return
So in the first case (3 to 5), it directly uses the value 90
for printing, while in the second case (8 to 15), it has to store the value into the variable and then load it back onto the stack. Then the print
method will pick the top value of the stack.
So, in case of the assignment:
byte x = i;
The value of i
will be picked up from the stack at runtime, and not inlined by the compiler. So the compiler doesn't know what value i
might contain.
Of course, this is all my guess. The byte code might be different on different JVMs. But I've the strong intuition that this might be the reason.
Also, JLS §4.12.4 might be relevant here:
A variable of primitive type or type String, that is final and initialized with a compile-time constant expression (§15.28), is called a constant variable.
Since in the second case, the variable is not initialized by a constant expression, but is later assigned a value, it is no longer a constant variable.