Why does Java require an explicit cast on a final variable if it was copied from an array?

JavaArraysCastingIntByte

Java Problem Overview


Starting with the following code...

byte foo = 1;
byte fooFoo = foo + foo;

When I try compiling this code I will get the following error...

> Error:(5, 27) java: incompatible types: possible lossy conversion from int to byte

... but if foo is final...

final byte foo = 1;
final byte fooFoo = foo + foo;

the file will compile successfully.

Moving on to the following code...

final byte[] fooArray = new byte[1];
fooArray[0] = 1;

final byte foo = fooArray[0];
fooArray[0] = 127;

System.out.println("foo is: " + foo);

... will print

foo is: 1

... which is fine. The value is copied to a final variable and it can not be changed any more. Playing with the value in the array does not change the value of the foo (as expected...).

Why does the following require a cast?

final byte[] fooArray = new byte[1];
fooArray[0] = 1;
final byte foo = fooArray[0];
final byte fooFoo = foo + foo;

How is this different than the second example in this question? Why is the compiler giving me the following error?

> Error:(5, 27) java: incompatible types: possible lossy conversion from int to byte

How can this happen?

Java Solutions


Solution 1 - Java

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, or int: > > - A narrowing primitive conversion may be used if the type of the variable is byte, short, or char, 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 is final 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 not final.

  • final byte foo = 1; does define a constant variable, because it's final 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 - Java

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 - Java

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:

Solution 4 - Java

This occurs because of

byte foo = 1;
byte fooFoo = foo + foo;

foo + foo = 2 will be answered but 2 is not byte type because of java has default data type to integer variables it's type is int. So you need to tell the compiler by force that answer must be a byte type explicitly.

class Example{
	public static void main(String args[]){
		
		byte b1 = 10;
		byte b2 = 20;
		byte b1b2 = (byte)(b1 + b2); 
		
		//~ b1 += 100;  // (+=) operator automaticaly type casting that means narrow conversion
		
		int tot = b1 + b2;
		
		//~ this bellow statement prints the type of the variable
		System.out.println(((Object)(b1 + b2)).getClass());  //this solve your problem
	}
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKoray TugayView Question on Stackoverflow
Solution 1 - JavashmoselView Answer on Stackoverflow
Solution 2 - JavaGhostCatView Answer on Stackoverflow
Solution 3 - JavaTonyView Answer on Stackoverflow
Solution 4 - JavaDhanushka sasankaView Answer on Stackoverflow