我发现了一段从Java 7切换到Java 8后停止编译的代码。它没有任何新的Java 8特性,比如lambda或stream。
我将有问题的代码范围缩小到以下情况:
GenericData<Double> g = new GenericData<>(1d);
Double d = g == null ? 0 : g.getData(); // type error!!!
您可能会猜到,GenericData
的构造函数有一个该泛型类型的参数,而getData()
方法只返回该泛型类型。(有关完整的源代码,请参阅下面)。
现在让我困扰的是,在Java 7中,代码编译得很好,而在Java 8中,我得到了以下错误:
CompileMe.java:20: error: incompatible types: bad type in conditional expression
Double d = g == null ? 0 : g.getData();
^
int cannot be converted to Double
看起来Java7能够完成从int -> Double -> Double的转换,但是Java8尝试立即从int -> double转换失败了。
我发现特别有趣的是,当我将代码从getData()
更改为data
时,Java8确实接受代码,即通过变量本身而不是GenericData
方法来访问方法的值:
Double d2 = g == null ? 0 : g.data; // now why does this work...
所以我这里有两个问题:
完整的源代码:
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;
}
}
要测试它,请运行编译器,如下所示:
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)
最后,以防万一:我运行的是Windows8和Java 1.8.0_112 (64位)。
发布于 2017-01-11 23:36:05
方法调用表达式是特殊的,因为它们可以是Poly表达式,取决于目标类型。
考虑以下示例:
static Double aDouble() {
return 0D;
}
…
Double d = g == null ? 0 : aDouble();
编译过程中没有任何问题
static <T> T any() {
return null;
}
…
Double d = g == null ? 0 : any();
在这里,any()
的调用是一个Poly表达式,编译器必须推断T := Double
。这将重现相同的错误。
这是第一个不一致的地方。虽然您的方法getData()
引用了GenericData
的类型参数T
,但它不是泛型方法(这里不涉及/应该不涉及类型推断来确定T
为Double
。
如果方法声明了一个或多个类型变量,则该方法是泛型的
getData()
没有声明类型变量,它只使用了一个。
JLS §15.12. Method Invocation Expressions:
如果满足以下所有条件,则方法调用表达式是多边形表达式:
由于此方法调用不是多重表达式,因此它的行为应该类似于使用aDouble()
调用的示例,而不是any()
。
但请注意§15.25.3
请注意,引用条件表达式不必将多边形表达式作为操作数包含,以便成为多边形表达式。由于它出现的上下文,它是一个多重表达式。例如,在下面的代码中,条件表达式是一个多边形表达式,并且每个操作数都被视为位于针对
Class<? super Integer>
__的赋值上下文中:
Class<?超级返回选择(boolean b,c1类,c2类){ Integer> b?c1 : c2;}
那么,它是引用条件表达式还是数值条件表达式呢?
§15.25. Conditional Operator ? :说:
根据第二个和第三个操作数表达式,有三种条件表达式:布尔条件表达式、数值条件表达式和引用条件表达式。分类规则如下:
…
为了对条件进行分类,以下表达式是数值表达式:
因此,根据这些规则,不排除泛型方法调用,所有显示的条件表达式都是数值条件表达式,并且应该起作用,因为只有“否则”它们才被认为是引用条件表达式。我测试的Eclipse版本编译了所有这些代码,但没有报告任何错误。
这导致了奇怪的情况,对于any()
的情况,我们需要目标类型来发现它有一个数值返回类型,并推断条件是一个数值条件表达式,即一个独立的表达式。请注意,对于布尔条件表达式,有以下备注:
请注意,对于泛型方法,这是实例化方法的类型参数之前的类型。
但是对于数值条件表达式,无论是有意还是无意,都没有这样的注释。
但如上所述,这只适用于any()
示例,因为getData()
方法不是泛型的。
发布于 2017-01-12 22:31:13
这似乎是Oracle编译器的一个已知问题:Bug ID: JDK-8162708
引用:
对问题的描述:
如果在泛型类中有一个方法声明如下:
类Foo { public T getValue() { //返回值... }}
在一个三元运算符中调用上面的方法,如下所示
Foo foo =新的Foo<>();浮动f=新的随机().nextBoolean()?foo.getValue():0f;
您会从javac 1.8编译器中得到一个语法错误。
但是上面的代码使用javac 1.7和1.9编译时没有错误和警告。
解决方案:未解决
受影响的版本:8
从评论中:
此问题仅适用于8u,在7和9
中没有问题
发布于 2017-01-11 23:14:23
不得不说,这不是一个答案,仅仅是一个推理。以我在编译器方面的短暂经验(不是特定于Javac ),它可能与代码的解析方式有关。
在下面的反编译代码中,您可以看到调用方法GenericData.getData:()Ljava/lang/Object
或引用字段GenericData.data:Ljava/lang/Object
,它们都首先获得返回Object的值/方法,然后是一个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 -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
这两个版本没有显著差异。所以我不认为有一个隐藏的秘密在做所有的黑魔法。这是编译器如何解析整个表达式的结果,并基于上下文来找出一个类型,使所有组件都同样满意。例如,
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
尽管如此,令人困惑的是为什么泛型字段可以工作,而泛型方法不能...
val = val == null ? 0 : g.data; // OK
val = val == null ? 0 : g.getData(); // Compilation error
编辑: Holger引用的文档似乎是一个很好的澄清。
https://stackoverflow.com/questions/41590024
复制相似问题