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?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Morty
  • 1,706
  • 1
  • 12
  • 25
  • 17
    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. – leonbloy Apr 20 '13 at 12:15
  • 1
    Which JDK are you using? It might also have to do with different vendors ... – barfuin Apr 20 '13 at 12:48
  • 1
    @leonbloy: Good point about simplification, I did simplify it somewhat (from my original code) but somehow stopped too early! – Morty Apr 20 '13 at 15:20
  • @Thomas: It was the latest JDK (for each version) from Oracle that I used. – Morty Apr 20 '13 at 15:21
  • @Morty nice research done.+1. – Geek Apr 24 '13 at 06:46
  • 2
    Another reason to never use autoboxing. – gyorgyabraham Apr 24 '13 at 08:05

2 Answers2

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

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
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;

Tim
  • 41,901
  • 18
  • 127
  • 145
leonbloy
  • 73,180
  • 20
  • 142
  • 190
  • 2
    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. – Morty Apr 22 '13 at 17:46