Java 8 autoboxing + generics: different behaviour with variable vs. method
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
, ie 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:
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).
Method invocation expressions are special in that they may be Poly Expressions, subject to target typing.
Consider the following examples:
static Double aDouble() {
return 0D;
}
…
Double d = g == null ? 0 : aDouble();
this compiles without any problems
static <T> T any() {
return null;
}
…
Double d = g == null ? 0 : any();
here, the invocation of any()
is a Poly Expression and the compiler has to infer T := Double
. This reproduces the same error.
This is the first inconsistency. While your method getData()
refers to the type parameter T
of GenericData
, it is not a generic method (there is/should be no type inference involved to determine that T
is Double
here.
A method is generic if it declares one or more type variables
getData()
doesn't declare type variables, it only uses one.
A method invocation expression is a poly expression if all of the following are true:
Since this method invocation is not a poly expression, it should behave like the example with the aDouble()
invocation, rather than the any()
.
But note §15.25.3:
Note that a reference conditional expression does not have to contain a poly expression as an operand in order to be a poly expression. It is a poly expression simply by virtue of the context in which it appears. For example, in the following code, the conditional expression is a poly expression, and each operand is considered to be in an assignment context targeting Class<? super Integer>
Class<? super Integer>
:
Class<? super Integer> choose(boolean b,
Class<Integer> c1,
Class<Number> c2) {
return b ? c1 : c2;
}
So, is it a reference conditional or a numeric conditional expression?
§15.25. Conditional Operator ? : says:
There are three kinds of conditional expressions, classified according to the second and third operand expressions: boolean conditional expressions, numeric conditional expressions, and reference conditional expressions. The classification rules are as follows:
…
For the purpose of classifying a conditional, the following expressions are numeric expressions:
So according to these rules, not precluding generic method invocations, all of the shown conditional expressions are numeric conditional expression and should work, as only “otherwise” they are to be considered to be reference conditional expression. The Eclipse version, I tested, compiled all of them without reporting any error.
This leads to the strange situation that for the any()
case we need target typing to find out that it has a numeric return type and deducing that the conditional is a numeric conditional expression, ie a stand-alone expression. Note that for boolean conditional expressions, there is the remark:
Note that, for a generic method, this is the type before instantiating the method's type arguments.
but for numeric conditional expression, there's no such note, whether intentional or not.
But as said, this only applies to the any()
example anyway, as the getData()
method is not generic.
This seems to be a known issue of the Oracle compiler: Bug ID: JDK-8162708
Quote:
A DESCRIPTION OF THE PROBLEM :
If you have a method in a generic class declared as follow:
class Foo<T> {
public T getValue() {
// returns a value ...
}
}
and you call the method above inside a ternary operator as follow
Foo<Integer> foo = new Foo<>();
Float f = new Random().nextBoolean() ? foo.getValue() : 0f;
you get a syntax error from the javac 1.8 compiler.
But the code above compiles with no errors and warnings with both javac 1.7 and 1.9.
Resolution: Unresolved
Affected Versions: 8
From the Comments:
This issue is only applicable to 8u, there is no issue in 7 and 9
Have to claim this is not an answer, merely a reasoning. With my brief experience in compiler (not Javac specific), it could has something to do with how the code is parsed.
In the following decompiled code, you see either calling the method GenericData.getData:()Ljava/lang/Object
or referring to field GenericData.data:Ljava/lang/Object
, they both first get the value/method with Object returned, followed by a cast
.
stack=4, locals=4, args_size=1
0: new #2 // class general/GenericData
3: dup
4: dconst_1
5: invokestatic #3 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
8: invokespecial #4 // Method general/GenericData."<init>":(Ljava/lang/Object;)V
11: astore_1
12: aload_1
13: ifnonnull 23
16: dconst_0
17: invokestatic #3 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
20: goto 30
23: aload_1
24: invokevirtual #5 // Method general/GenericData.getData:()Ljava/lang/Object;
27: checkcast #6 // class java/lang/Double
30: astore_2
31: aload_1
32: ifnonnull 39
35: dconst_0
36: goto 49
39: aload_1
40: getfield #7 // Field general/GenericData.data:Ljava/lang/Object;
43: checkcast #6 // class java/lang/Double
46: invokevirtual #8 // Method java/lang/Double.doubleValue:()D
49: invokestatic #3 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
52: astore_3
53: return
If compare the ternary operator expression with an equivalent if-else:
Integer v = 10;
v = v != null ? 1 : 0;
0: bipush 10
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: aload_1
7: ifnull 14
10: iconst_1
11: goto 15
14: iconst_0
15: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
18: astore_1
19: return
Integer v = 10;
if (v != null)
v = 1;
else
v = 0;
0: bipush 10
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: aload_1
7: ifnull 18
10: iconst_1
11: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
14: astore_1
15: goto 23
18: iconst_0
19: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
22: astore_1
23: return
There is no significant difference in the two versions. So I don't think there is a hidden secret doing all the black magic. It's a result of how the compiler parse the whole expression, and based on the context to figure out a type to make all components equally happy. Eg,
Double val = 0; // compilation error: context is clear, 0 is an integer, so Integer.valueOf(i), but don't match expected type - Double
val = 0 + g.getData(); // OK, enough context to figure out the type should be Double
Still, the confusion is in that why the generic field works but not the generic method...
val = val == null ? 0 : g.data; // OK
val = val == null ? 0 : g.getData(); // Compilation error
EDIT: the document Holger quoted seems to be a good clarification.
链接地址: http://www.djcxy.com/p/73660.html上一篇: 分组类似的盒子