Java casting: is the compiler wrong, or is the language spec wrong, or am I wrong?
Asked Answered
D

2

20

I have been reading the Java Language Spec, 3rd edition, and have found what I think is a discrepancy between the spec and the javac compiler implementation. The same discrepancies exist in the Eclipse compiler.

Section 15.16 talks about cast expressions. It says that it should be a compile time error if the argument type cannot be converted to the cast type via casting conversion (section 5.5):

It is a compile-time error if the compile-time type of the operand may never be cast to the type specified by the cast operator according to the rules of casting conversion (§5.5). Otherwise, at run-time, the operand value is converted (if necessary) by casting conversion to the type specified by the cast operator.

Section 5.5 talks about casting conversion. It gives a list of conversion types which are allowed. Specifically absent from the list is "unboxing conversion followed by widening/narrowing primitive conversion". However that exact sequence of conversions does seem to be allowed by the javac compiler (and also the Eclipse compiler). For instance:

long l = (long) Integer.valueOf(45);

... compiles just fine. (The problematic cast is the cast to long; the argument is of type java.lang.Integer, so the conversion requires unboxing to int followed by a widening primitive conversion).

Likewise, according to the JLS it should not be possible to cast from byte to char, because that (according to 5.1.4) requires a widening primitive conversion and a narrowing primitive conversion - however, this cast is also allowed by the compilers.

Can anyone enlighten me?

Edit: since asking this, I have filed a bug report with Oracle. Their response is that this is a "glitch in the JLS".

Discernible answered 22/3, 2011 at 1:28 Comment(1)
A better example would be long l = (long) Integer.valueOf(45);Br
W
3

I think you are right, the compilers are right, and the spec is wrong....

This compiles: (Object)45 and this does not: (Long)45

The only way to make sense of the compilers' behavior (including Intellij I'm using) is if Casting Conversion is modified to agree with Assignment Conversion and Method Invocation Conversion:

  • a boxing conversion (§5.1.7) optionally followed by widening reference conversion

  • an unboxing conversion (§5.1.8) optionally followed by a widening primitive conversion.

plus

  • widening and narrowing primitive convesion

The spec did say "casting conversions are more inclusive than assignment or method invocation conversions: a cast can do any permitted conversion other than a string conversion or a capture conversion"

Woodrum answered 22/3, 2011 at 2:45 Comment(5)
(Object)45 is just a boxing operation, right? does (Long)45L work? i think (Long)45 is technically 2 things, boxing, then casting to Long, which doesn't work, because 45 would be boxed to an Integer, not a Long.Recency
(Object)45 is "boxing conversion followed by widening reference conversion", legal under proposed rules. (Long)45 could work if we were to allow combination of rules, i.e. "widening primitive" + "boxing"Woodrum
I think you're right. I've noticed that the method invocation and assignment conversions specifically say that you can use only one of the listed conversion sequences, whereas casting conversion does not specifically state that only one may be used: however, allowing an arbitrary selection would allow, for instance, an unnecessary primitive narrowing conversion (followed by a widening) - which could alter the value - in a case where the identity conversion would have sufficed.Discernible
(Also, allowing an arbitrary selection would allow your example of (Long)45, of course).Discernible
JLS is not of the highest quality. Pretty messy in lots of places.Woodrum
J
1

By my reading, the cast from int to long is permitted by this clause:

A value of a primitive type can be cast to another primitive type by identity conversion, if the types are the same, or by a widening primitive conversion or a narrowing primitive conversion.

Converting int to long is a widening primitive conversion.

That just leaves the conversion from Integer to int, which is accommodated by the last bullet:

an unboxing conversion

Of course, the cast to long isn't even necessary in the example.

Consider the following four definitions:

final Integer io = Integer.valueOf(45);
final int i = io;
final long l1 = (long)i;
final long l2 = i;

Do you consider any of them surprising? Your original example doesn't look any different; it merely elides the intermediate variables.

Jose answered 22/3, 2011 at 2:28 Comment(4)
You're implying that all of the conversions listed in 5.5 can optionally be applied, and in any order? (the order required by my example is for the unboxing conversion to happen first, followed by a widening). That makes listing the identity conversion redundant, and also makes it unclear why the unchecked conversion is special cased (by combining it specifically with widening/narrowing reference conversions). I am fairly sure that only one of the listed conversions may be applied.Discernible
Although: I do note that casting conversion doesn't specifically say that only one of the listed conversion sequences may be applied, as Method Invocation Conversion and Assignment Conversion do.Discernible
In fact, the detailed rules in 5.5 explicitly say that the entire procedure is in some cases applied recursively. That says to me that the rules can most certainly be applied more than once.Mopey
@Ted, can you give me the exact location / text of that? The closest I can find is: "If T is a type variable, then this algorithm is applied recursively, using the upper bound of T in place of T." - but that only applies for casting from one reference type to another.Discernible

© 2022 - 2024 — McMap. All rights reserved.