Why does Java require an explicit cast on a final variable if it was copied from an array?
Solution 1:
The JLS (§5.2) has special rules for assignment conversion with constant expressions:
In addition, if the expression is a constant expression (§15.28) of type
byte
,short
,char
, orint
:
- A narrowing primitive conversion may be used if the type of the variable is
byte
,short
, orchar
, and the value of the constant expression is representable in the type of the variable.
If we follow the link above, we see these in the definition of constant expression:
- Literals of primitive type and literals of type
String
- The additive operators
+
and-
- Simple names (§6.5.6.1) that refer to constant variables (§4.12.4).
If we follow the second link above, we see that
A variable of primitive type or type
String
, that isfinal
and initialized with a compile-time constant expression (§15.28), is called a constant variable.
It follows that foo + foo
can only be assigned to fooFoo
if foo
is a constant variable. To apply that to your cases:
-
byte foo = 1;
does not define a constant variable because it's notfinal
. -
final byte foo = 1;
does define a constant variable, because it'sfinal
and initialized with a constant expression (a primitive literal). -
final byte foo = fooArray[0];
does not define a constant variable because it's not initialized with a constant expression.
Note that whether fooFoo
is itself final
doesn't matter.
Solution 2:
The value 1 fits nicely into a byte; so does 1+1; and when the variable is final, the compiler can do constant folding. (in other words: the compiler doesn't use foo
when doing that + operation; but the "raw" 1 values)
But when the variable is not final, then all the interesting rules about conversions and promotions kick in (see here; you want to read section 5.12 about widening primitive conversions).
For the second part: making an array final still allows you to change any of its fields; so again; no constant folding possible; so that "widening" operation is kicking in again.
Solution 3:
It is indeed what compiler do in constant folding when used with final
, as we can see from byte code:
byte f = 1;
// because compiler still use variable 'f', so `f + f` will
// be promoted to int, so we need cast
byte ff = (byte) (f + f);
final byte s = 3;
// here compiler will directly compute the result and it know
// 3 + 3 = 6 is a byte, so no need cast
byte ss = s + s;
//----------------------
L0
LINENUMBER 12 L0
ICONST_1 // set variable to 1
ISTORE 1 // store variable 'f'
L1
LINENUMBER 13 L1
ILOAD 1 // use variable 'f'
ILOAD 1
IADD
I2B
ISTORE 2 // store 'ff'
L2
LINENUMBER 14 L2
ICONST_3 // set variable to 3
ISTORE 3 // store 's'
L3
LINENUMBER 15 L3
BIPUSH 6 // compiler just compute the result '6' and set directly
ISTORE 4 // store 'ss'
And if you change your final byte to 127, it will also complain:
final byte s = 127;
byte ss = s + s;
in which cases, the compiler compute the result and know it out of limit, so it will still complain they are incompatible.
More:
And here is another question about constant folding with string: