Differences in auto-unboxing between Java 6 vs Java 7
I have noted a difference in auto unboxing behavior between Java SE 6 and Java SE 7. I'm wondering why that is, because I can't find any documentation of changes in this behavior between these two versions.
Here's a simple example:
Object[] objs = new Object[2];
objs[0] = new Integer(5);
int myInt = (int)objs[0];
This compiles fine with javac from Java SE 7. However, if I give the compiler the "-source 1.6" argument I get an error on the last line:
inconvertible types
found : java.lang.Object
required: int
I tried downloading the Java SE 6 to compile with the native version 6 compiler (without any -source option). It agrees and gives the same error as above.
So what gives? From some more experimentation it seems that the unboxing in Java 6 can only unbox values that clearly (at compile time) is of the boxed type. For instance, this works in both versions:
Integer[] objs = new Integer[2];
objs[0] = new Integer(5);
int myInt = (int)objs[0];
So it seems that between Java 6 and 7, the unboxing feature was enhanced so that it could cast and unbox object types in one swoop, without knowing (at compile time) that the value is of the proper boxed type. However, reading through the Java Language Specification or blog postings that were written at the time Java 7 came out, I can't see any change of this thing, so I'm wondering what the change is and what this "feature" is called?
Just a curiosity: Due the change, it is possible to trigger "wrong" unboxings:
Object[] objs = new Float[2];
objs[0] = new Float(5);
int myInt = (int)objs[0];
This compiles fine but gives a ClassCastException at runtime.
Any reference on this?
Solution 1:
It looks like the language in section 5.5 Casting Conversion of Java 7 JLS was updated in comparison to the same section in the Java 5/6 JLS, probably to clarify the allowed conversions.
Java 7 JLS says
An expression of a reference type may undergo casting conversion to a primitive type without error, by unboxing conversion.
Java 5/6:
A value of a reference type can be cast to a primitive type by unboxing conversion (§5.1.8).
The Java 7 JLS also contains a table (table 5.1) of allowed conversions (this table is not included in the Java 5/6 JLS) from reference types to primitives. This explicitly lists casts from Object to primitives as a narrowing reference conversion with unboxing.
The reason is explained in this email:
Bottom line: If the spec. allows (Object)(int) it must also be allowing (int)(Object).
Solution 2:
You are right; to put it more simply:
Object o = new Integer(1234);
int x = (int) o;
This works in Java 7, but gives a compilation error in Java 6 and below. Strangely, this feature is not prominently documented; for example, it's not mentioned here. It's debatable if it's a new feature or a bug fix (or a new bug?), see some related info and discussion. The consensus seems to point to an ambiguity in the original spec, which led to a slightly incorrect/inconsistent implementation on Java 5/6, which was fixed in 7, because it was critical for implementation of JSR 292 (Dynamically Typed Languages).
Java autoboxing has now some more traps and surprises. For example
Object obj = new Integer(1234);
long x = (long)obj;
will compile, but fail (with ClassCastException
) at runtime. This, instead, will work:
long x = (long)(int)obj;