Explanation
Let's take a look at your code and some modified examples:
// Example 1
byte byteValue = 2;
// Example 2
byte byteValue = 4/2;
// Example 3
byte byteValue = 2000;
// Example 4
byte byteValue = 500/2;
// Example 5
int n1 = 4;
byte byteValue = n1/2;
Non-lossy conversion
You will get the mentioned compile-time error for Example 3, Example 4 and Example 5.
First of all, the simple math you have for Example 1 to 4 is executed at compile-time. So Java will compute 500 / 2
at compile-time and replace the code with basically byte byteValue = 250;
.
Valid values for bytes in Java are -128
to 127
. So any value outside of that range can not just be taken as a byte
but requires explicit conversion. Because of that, Example 1 and Example 2 pass.
Lossy narrowing conversion
To understand why the rest fails, we have to study the Java Language Specification (JLS), more specifically chapter 5.1.3. Narrowing Primitive Conversion and 5.2. Assignment Contexts.
It says that a conversion from int
to byte
(if it is outside of the range of byte
) is a narrowing primitive conversion and that it may lose information (for obvious reasons). It continues by explaining how the conversion is done:
A narrowing conversion of a signed integer to an integral type T simply discards all but the n lowest order bits, where n is the number of bits used to represent type T. In addition to a possible loss of information about the magnitude of the numeric value, this may cause the sign of the resulting value to differ from the sign of the input value.
From the second chapter, assignments with narrow conversions are allowed if the value is a constant expression.
In addition, if the expression is a constant expression (§15.29) of type byte
, short, char, or int:
A narrowing primitive conversion may be used if the variable is of type byte
, short, or char, and the value of the constant expression is representable in the type of the variable.
Long story short, a narrowing conversion that may lose information (because the value exceeds the range) has to explicitly be announced to Java. Java will not just do it for you without you forcing it. That is done by a cast.
So for example
byte byteValue = (byte) (500 / 2);
resulting in the value -6
.
Constant expression
Your last example is very interesting:
int n1 = 4;
byte byteValue = n1/2;
Although this does not exceed the range, Java still treats it as lossy narrowing conversion. Why is that the case?
Well, Java can not ensure 100% that n1
is not changed last second before n1/2
is executed. Therefore, it would have to consider all of your code to see if maybe someone accesses n1
sneaky and changes it. Java does not do this kind of analysis at compile-time.
So if you can tell Java that n1
stays 4
and can actually never change, then this will actually compile. In this specific case, it would be enough to make it final
. So with
final int n1 = 4;
byte byteValue = n1/2;
it will actually compile because Java knows that n1
stays 4
and can not change anymore. Hence it can compute n1/2
at compile-time to 2
and replace the code with basically byte byteValue = 2;
, which is in range.
So you made n1 / 2
a constant expression, as explained before in 5.2. Assignment Contexts.
You can check the details what it needs to have a constant expression in 15.29. Constant Expressions. Basically everything simple that can easily be computed in place without any method invocations or other fancy stuff.