12

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

specializt
  • 1,913
  • 15
  • 26
Axel
  • 13,939
  • 5
  • 50
  • 79
  • This is not something the JLS allows. – Peter Lawrey May 08 '15 at 09:54
  • 2
    @PeterLawrey: I didn't expect so either, but javac 1.7 on Linux appears to allow it. Interesting. – Jon Skeet May 08 '15 at 09:55
  • It should work if you make it `double nr = n == null ? 0.0 : (Double) n;` – Peter Lawrey May 08 '15 at 09:55
  • @specializt I think it is about programming since I want to know whether it is allowed in the first place. – Axel May 08 '15 at 09:55
  • It works for me in javac 1.8 on Windows too. Very odd,. – Jon Skeet May 08 '15 at 09:56
  • 2
    @PeterLawrey Yes, but I chose to use `doubleValue()` because that one also works if the actual class is `Integer` (the value comes from some library, and it could be next release returns Integer instead of Double for integral values). – Axel May 08 '15 at 10:00
  • 2
    you had the wrong tag - this works because `Number` allows for automatic `unboxing`. Your example is valid but its not recommended - you should stick to `doubleValue` and the likes. Note that unboxing can only happen for/to `integral primitives` in the case of `Number` – specializt May 08 '15 at 10:00
  • 1
    @specializt I am pretty sure `Number` doesn't support implicit unboxing. – Peter Lawrey May 08 '15 at 10:04
  • @PeterLawrey It does, as per the [official Java tutorials page on Number classes](https://docs.oracle.com/javase/tutorial/java/data/numberclasses.html) – kaykay May 08 '15 at 10:09
  • 2
    @kaykay On the first link on that page `Converting an object of a wrapper type (Integer) to its corresponding primitive (int) value is called unboxing` `Number` has no corresponding primitive type. – Peter Lawrey May 08 '15 at 10:12
  • The reason why the code doesn't compile might be caused by compiler settings and be independant of the Eclipse version. The Eclipse compiler can be (per project or per workspace) advised to treat autoboxing as an error. – Rüdiger Herrmann May 08 '15 at 10:39
  • @RüdigerHerrmann: Yes, checked that. Same settings in both versions of eclipse. – Axel May 08 '15 at 10:59
  • Looking this up in [Chapter 5 of the JLS](https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.1.8) I still think the unboxing from `Number` to `double` should not be possible. – Axel May 08 '15 at 11:03
  • that is the JLS for Java 7 - an obsolete version of java. The most recent version is 8u45. Additionally : it is clearly stated that objects like `Double`may be unboxed to `double` --- `Double` is a `Number` so `Number` itself may also be unboxed independently but precision-losses may occur since the exact implementation of `Number` is not known without additional effort. I think you should stop digging through JLS and start digging through actual sourcecode, a lot of things are easy to understand all of sudden once you look at reality instead of theory. – specializt May 14 '15 at 15:58
  • 1
    @specializt "that is the JLS for Java 7 - an obsolete version of java." - That's the version of Java we have to use for the forseeable future. Have you ever worked in an enterprise environment? "Oracle Java SE product releases - starting with Java SE 7 - are supported for no less than eleven (11) years from initial release date, enabling IT managers and ISVs to plan their upgrades according to their individual business practices." Source: [Oracle Java SE Support Roadmap](http://www.oracle.com/technetwork/java/eol-135779.html) – Axel May 14 '15 at 17:33
  • PS: ...and I have only little hope we can upgrade before premier support runs out in Jul 2019 since we just moved from Java 6 in Dec 2014. :-( – Axel May 14 '15 at 17:48
  • that is one faulty company you're working in ... you should switch jobs. – specializt May 14 '15 at 19:17

3 Answers3

6

With Java 7, the casting system had to be changed slightly with regards to primitive types in order to allow working with MethodHandles. 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.

Community
  • 1
  • 1
Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
0

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)

  1. realizing that since n is a number the cast has to change from double to Double
  2. unboxing
Giovanni
  • 3,951
  • 2
  • 24
  • 30
  • The problem with "the Java compiler seems smart to perform the conversion under the hood" is this sentence from the JLS: "Any conversion that is not explicitly allowed is forbidden. " – Axel May 08 '15 at 11:53
  • @Axel my phrase is better explained after; there are two conversion first from double to Double and then the autoboxing and each of them is allowed; I think that in this case the JLS should be more detailed – Giovanni May 08 '15 at 12:33
-1

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);
  • The second line is just what I posted, and it is accepted by one compiler version but rejected by the other. As I read the JLS, this should be an error. What makes me think though is that both eclipse 4.4.2 and javac accept it. – Axel May 08 '15 at 12:09
  • This is the OFFICIAL DOCUMENTATION https://docs.oracle.com/javase/7/docs/api/java/lang/Number.html Number is a superclass of classes BigDecimal, BigInter, Byte, Double, Float, Integer, Long, and Short. and "**Autoboxing and unboxing** allows a primitive to be used interchangeably as its object **wrapper class**" (E. Finegan & R. Liguori - OCA Java SE7 pag.151). So if you have a compiler that compile a Number as a wrapper class this is surely: 1) a bad coding practice 2) a problem with the compiler. Please use documentation when you want assert something. – Francesco Capodanno May 09 '15 at 23:17
  • Yes, it looks like they had to change the compiler and forgot to update the documentation. See the accepted answer and links given for details. PS: I'm not the downvoter... ;-) – Axel May 10 '15 at 17:29
  • However I think that it is not a good practice to use autoboxing with Number (in JAVA 7) The code without explicit casting don´t work in many compilers and the idea of autoboxing born with a different approach. If the Documentation is not updated also many compilers and many programmers are not updated. So if you use this method and this work good for you. But I continue to use explcit casting and mantain the compatibility with many compilers (Java 7) and many programmers :) The code with autoboxing for Number don´t work also in my Netbeans 8.0.2 in Linux ( not only eclipse problem). Thanks. – Francesco Capodanno May 10 '15 at 18:08
  • Naturally if you work with Java 8 all change and I´m in according with the acceppted answer. But not with Java 7 and many Java 7 compilers. – Francesco Capodanno May 10 '15 at 18:10