I found a piece of code that after switching from Java 7 to Java 8 stopped compiling. It does not feature any of the new Java 8 stuff like lambda or streams.
I narrowed the problematic code down to the following situation:
GenericData<Double> g = new GenericData<>(1d);
Double d = g == null ? 0 : g.getData(); // type error!!!
You can probably guess that GenericData
's constructor has one parameter of that generic type and the getData()
method returns just that generic type. (For the complete source code see below.)
Now what bothers me is that in Java 7 that code compiled just fine whereas with Java 8 I get the following error:
CompileMe.java:20: error: incompatible types: bad type in conditional expression
Double d = g == null ? 0 : g.getData();
^
int cannot be converted to Double
It seems that Java 7 was able to do the transition from int -> double -> Double, but Java 8 fails with trying to immediately go from int -> Double.
What I find funny in particular is that Java 8 does accept the code when I change it from getData()
to data
, i.e. access the GenericData
's value via the variable itself instead of the getter-method:
Double d2 = g == null ? 0 : g.data; // now why does this work...
So the two questions I have here are:
- Why doesn't Java 8 infer the types like Java 7 and cast my int to double before autoboxing double to Double?
- Why does that problem only occur with the generic method but not the generic variable?
Complete source code:
public class CompileMe {
public void foo() {
GenericData<Double> g = new GenericData(1d);
Double d = g == null ? 0 : g.getData(); // type error!!!
Double d2 = g == null ? 0 : g.data; // now why does this work...
}
}
class GenericData<T> {
public T data;
public GenericData(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
To test it run the compiler as follows:
javac -source 1.7 -target 1.7 CompileMe.java # ok (just warnings)
javac -source 1.8 -target 1.8 CompileMe.java # error (as described above)
Finally in case it matters: I run Windows 8 and Java 1.8.0_112 (64-bit).
g = new GenericData
. Probably not the root cause, but for sure doesn't help. And btw: I just repro'ed the issue with IBM JDK 1.8 ... really interesting. – Distraughtg.data
seems to unbox and immediately rebox it. If you pass anull
to theGenericData
constructor, the line withg.data
will throw an NPE. Using0d
, the line withg.getData()
passes, but the one withg.data
still throws an NPE. – Unplug0d
) does not throw NPE as you said, and instead results in outputting simply "null". That explains why one needs0d
instead of0
: Because no unboxing takes place and int can't be converted to Double implicitely. Still, why do the method call and the field access show different behaviour... – Moint
, 3rd operandDouble
the resulting type isbnp(int,Double)
(binary numeric promotion) which in turn states "If any operand is of a reference type, it is subjected to unboxing conversion" -> the NPE is to be expected – Bilberrydouble
. Possibly, the compiler infers the type of the 3rd operand to some other reference type, but I don't see a reason for this in the given snippet. – Bilberry