A colleague checked in this code:
Number n = ...;
double nr = n == null ? 0.0 : (double) n;
Another colleague then complained that this didn't compile, and that's what I would expect. However, it turned out that I already had pulled this code from SVN and everything worked fine. We all had our the Java version set to 1.7 in eclipse, and it turned out that the code compiles fine under eclipse 4.4.2 (Luna) but fails under 4.2.2.
I fixed the issue by replacing the cast by n.doubleValue()
.
Now the actual question is: why is this accepted in the first place? It should of course work when casting to Double
instead of double
, but I'd think that a direct cast from Number
to double
was disallowed. So, is this a bug in eclipse 4.2.2 that was fixed in the meantime, or does eclipse 4.4.2 silently accept code that should not compile (which would IMHO be a bug)?
With Java 7, the casting system had to be changed slightly with regards to primitive types in order to allow working with MethodHandle
s. When invoking a method handle, the javac compiler generates a so-called polymorhic signature which derives from the method handle's method signatures. These polymorphic signatures are created by hinting out a parameter's type with a casting. For example, when binding a method with a signature of double, long -> int
, the following casting is required:
Number foo = 42d, bar = 43L;
int ignored = (int) methodHandle.invoke((double) object, (long) bar);
The source code signature of MethodHandle::invoke
is however defined as Object[] -> Object
, without directly casting a value to a primitive type, the polymorphic signature could not be generated.
Obviously, for this to be possible, the Java compiler had to be changed to allow such castings which were not previously legal. While it would - in theory - be possible to restrict this use of castings to methods that are annotated with @PolymorhicSignature
, this would have resulted in a strange exception why it is now generally possible in javac where appropriate byte code is generated when not creating a polymorphic signature. Yet, primitive types still represent their own runtime types what was pointed out in the other answer that posted the generated byte code of such a casting outside of a MethodHandle
Object foo = 42;
int.class.cast(foo);
would result in a runtime exception.
However, I agree with the comments that this is not necessarily dicussed appropriately in the JLS but I found a thread mentioning this specification gap. It is mentioned that the specification should be updated accordingly once lambda expressions are put in place but the JLS for Java 8 does not seem to mention such castings or @PolymorphicSignature
either. At the same time, it states that [a]ny conversion that is not explicitly allowed is forbidden.
Arguably, the JLS is currently lagging behind the javac implementation and the Eclipse compiler has surely not picked this up properly. You could compare this to some corner-cases of generic type inference where several IDE compiler behave differently that javac until today.
As stated in the comments Number has no corresponding primitive type, but the Java compiler seems smart to perform the conversion under the hood once you put the cast.
Here is a sample test case
package test;
public class NumberAutoboxing {
public static void main(String[] args) {
Number n =new Long(1);
double nr = (double) n;
Integer i= 1;//boxing
Integer j= new Integer(1);
int k=j;//unboxing
System.out.print("n="+n+" nr=" + nr + " i="+ i + " k=" + k);
}
}
I decompiled the .class (I tested jdk 7 and 8 on windows and the result is the same) and this is the result
import java.io.PrintStream;
public class NumberAutoboxing
{
public static void main(String[] args)
{
Number n = new Long(1L);
double nr = ((Double)n).doubleValue();
Integer i = Integer.valueOf(1);
Integer j = new Integer(1);
int k = j.intValue();
System.out.print("n=" + n + " nr=" + nr + " i=" + i + " k=" + k);
}
}
As you can see the the conversion from number to double is made in the same way as in the unboxing sample (from Integer to int); that was because the compiler changed the cast from double
to Double
and thus allowing unboxing. It seems that there were two comilation fases (or something similar I'm speculating on this one)
- realizing that since n is a number the cast has to change from
double to Double
- unboxing
The abstract class Number is the superclass of classes BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, and Short.
Double is the wrapper class for double and support the auto-boxing but not Number. In others words Number can contain many wrapper classes that are not Double so you need an explicit casting.
Try this code:
Number n = 78.3145;
double nr = (double) n;
Double d = 3.1788;
double dr = d;
System.out.println(n);
System.out.println(dr);