I am mystified by the behavior of the Java compiler when assigning primitives to wrapper class references. Please see the code below. The lines with comments don't compile.
I don't understand the logic of why:
- a
byte
can be assigned to a Byte
or Short
, but not Integer
or Long
reference
- a
short
can be assigned to a Byte
or Short
, but not Integer
or Long
reference
- an
int
can be assigned to a Byte
, Short
, or Integer
, but not Long
reference
- a
long
can be assigned to a Long
, but not Byte
, Short
or Integer
reference
I cannot see the pattern. Any insight into this will be really helpful.
Thanks.
Byte s5 = (byte)7;
Short s6 = (byte)7;
Integer s7 = (byte)7; // Does not compile
Long s8 = (byte)7; // Does not compile
Byte s9 = (short)7;
Short s10 = (short)7;
Integer s11 = (short)7; // Does not compile
Long s12 = (short)7; // Does not compile
Byte s1 = (int)7;
Short s2 = (int)7;
Integer s3 = (int)7;
Long s4 = (int)7; // Does not compile
Byte s13 = (long)7; // Does not compile
Short s14 = (long)7; // Does not compile
Integer s15 = (long)7; // Does not compile
Long s16 = (long)7;
Let's look at the types of conversions allowed in an assignment context.
Principally:
Assignment contexts allow the use of one of the following:
an identity conversion
a widening primitive conversion
a widening reference conversion
a boxing conversion optionally followed by a widening reference conversion
an unboxing conversion optionally followed by a widening primitive conversion.
(Note my emphasis on one.)
Most of your examples that do not compile, for example
Integer s11 = (short)7;
require a widening primitive conversion followed by a boxing conversion. This is not a permitted conversion.
But then you might wonder why the following example does compile:
Byte s9 = (short)7;
This is a narrowing primitive conversion followed by a boxing conversion.
This is a special case:
In addition, if the expression is a constant expression of type byte
, short
, char
, or int
[...] a narrowing primitive conversion followed by a boxing conversion may be used if the type of the variable is:
Byte
and the value of the constant expression is representable in the type byte
.
Short
and the value of the constant expression is representable in the type short
.
Character
and the value of the constant expression is representable in the type char
.
This special case is necessary because there is no way to express an integer literal of a type narrower than int
.
This seems to be compiler-specific behavior. When I paste your code into Eclipse, running Java 7, I do not see the compiler errors you report for short
to Integer
or byte
to Integer
.
Instead, I see byte
, short
, and int
can all be assigned to Byte
, Short
, and Integer
, but not Long
, and long
can only be assigned to Long
. Interestingly, if you change the variables to primitives instead of wrapper types, the byte
, short
, and int
behavior doesn't change, but now the assignments from the other types to long
also work.
javac 1.7.0_02
| byte | Byte || short | Short || int | Integer || long | Long |
From byte | Yes | Yes || Yes | Yes || Yes | No || Yes | No |
From short | Yes | Yes || Yes | Yes || Yes | No || Yes | No |
From int | Yes | Yes || Yes | Yes || Yes | Yes || Yes | No |
From long | No | No || No | No || No | No || Yes | Yes |
Eclipse Indigo
| byte | Byte || short | Short || int | Integer || long | Long |
From byte | Yes | Yes || Yes | Yes || Yes | Yes || Yes | No |
From short | Yes | Yes || Yes | Yes || Yes | Yes || Yes | No |
From int | Yes | Yes || Yes | Yes || Yes | Yes || Yes | No |
From long | No | No || No | No || No | No || Yes | Yes |
Given that different compilers allow different conversions, I suspect the "correct" behavior is not actually spelled out in the JLS. It seems certain conversions are done under the covers because the compiler writers considered it convenient (e.g. byte a = (int)1
is allowed but byte a = (int)1000
is not), not because it's a documented part of the language.
From my research, I found that a byte is an 8-bit signed integer. Shorts are 16-bit signed integers. Therefore I can see why they are compatible, they are both two's complement signed integers, emphasis on signed. A long is a 64-bit integer, but it can also be unsigned (considering it has methods for comparing unsigned longs). This would probably explain why your conversions to long are causing errors - you'd be casting a signed byte to a potentially unsigned long. (Source: Reading about primitives at http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)