Differences in auto-unboxing between Java 6 vs Java 7
Asked Answered
B

2

107

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?

Bestride answered 20/4, 2013 at 11:10 Comment(6)
Interesting. A new ingredient for the autoboxing mess. I think your example could be more simple and clear with a single object instead of an array. Integer obj = new Integer(2); int x = (int)obj; : works on Java 7, gives error on Java 6.Glume
Which JDK are you using? It might also have to do with different vendors ...Crosse
@leonbloy: Good point about simplification, I did simplify it somewhat (from my original code) but somehow stopped too early!Bestride
@Thomas: It was the latest JDK (for each version) from Oracle that I used.Bestride
@Bestride nice research done.+1.Guildsman
Another reason to never use autoboxing.Cedar
H
93

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).

Heracliteanism answered 20/4, 2013 at 13:32 Comment(0)
G
35

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;

Glume answered 20/4, 2013 at 12:41 Comment(1)
Thanks for the answer. However, there's one thing I don't understand. This is a clarification of the JLS and the accompanying implementations (cf. the mail discussion), but why would that be done to accomodate other typed languages on the JVM? After all, it's a change to the language, not the VM: the VM's casting behavior works as it always did, the compiler implements this feature using the existing mechanism for casting to Integer and calling .intValue(). So how could this change in the Java language proper, help run other languages on the VM? I agree your link suggests this, just wondering.Bestride

© 2022 - 2024 — McMap. All rights reserved.