Java 8 autoboxing +泛型:变量与方法的不同行为
我发现从Java 7切换到Java 8后停止编译的一段代码。 它没有任何新的Java 8的东西,如lambda或流。
我将有问题的代码缩小到以下情况:
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
看起来Java 7能够从int - > double - > Double进行转换,但是Java 8试图立即从int - > Double进行失败。
我特别感到有趣的是,当我将它从getData()
更改为data
时,Java 8确实接受了代码,即通过变量本身而不是getter-method访问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)
最后以防万一:我运行Windows 8和Java 1.8.0_112(64位)。
方法调用表达式的特殊之处在于它们可以是聚合表达式,受到目标输入的限制。
考虑下面的例子:
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()
不声明类型变量,它只使用一个。
如果满足以下所有条件,则方法调用表达式是一个poly表达式:
由于此方法调用不是poly表达式,因此它应该像使用aDouble()
调用的示例而不是any()
。
但是注意§15.25.3:
请注意,引用条件表达式不必包含多表达式作为操作数以便成为多表达式。 这是一种多元表达,仅凭其出现的上下文而已。 例如,在下面的代码中,条件表达式是一个多表达式,并且每个操作数都被认为是在一个赋值上下文中,目标Class<? super Integer>
Class<? super Integer>
:
Class<? super Integer> choose(boolean b,
Class<Integer> c1,
Class<Number> c2) {
return b ? c1 : c2;
}
那么,它是一个引用条件表达式还是一个数字条件表达式?
§15.25。 有条件的运营商? :说:
有三种条件表达式,根据第二个和第三个操作数表达式分类:布尔条件表达式,数值条件表达式和引用条件表达式。 分类规则如下:
...
为了对条件进行分类,以下表达式是数字表达式:
因此,根据这些规则,不排除泛型方法调用,所有显示的条件表达式都是数值条件表达式,并且应该起作用,因为只有“否则”它们才被认为是引用条件表达式。 我测试过的Eclipse版本在不报告任何错误的情况下编译了所有这些版本。
这导致了奇怪的情况,对于any()
情况,我们需要进行目标打字,以发现它具有数字返回类型,并推断条件是数字条件表达式,即独立表达式。 请注意,对于布尔条件表达式,有这样的评论:
请注意,对于泛型方法,这是在实例化方法的类型参数之前的类型。
但对于数字条件表达式,不存在这样的注释,无论是否有意。
但正如所说的,无论如何,这只适用于any()
示例,因为getData()
方法不是通用的。
这似乎是Oracle编译器的一个已知问题:错误ID:JDK-8162708
引用:
问题描述:
如果您在泛型类中有一个方法声明如下:
class Foo<T> {
public T getValue() {
// returns a value ...
}
}
你可以在三元运算符里面调用上面的方法
Foo<Integer> foo = new Foo<>();
Float f = new Random().nextBoolean() ? foo.getValue() : 0f;
你会从javac 1.8编译器中得到一个语法错误。
但是上面的代码编译时没有javac 1.7和1.9的错误和警告。
解决方法:未解决
受影响的版本:8
来自评论:
这个问题只适用于8u,7和9没有问题
不得不声称这不是一个答案,只是一个推理。 凭借我在编译器方面的短暂经验(不是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引用的文档似乎是一个很好的说明。
链接地址: http://www.djcxy.com/p/73659.html上一篇: Java 8 autoboxing + generics: different behaviour with variable vs. method