Java泛型不一致行为?
为什么第一个方法编译,第二个不编译?Java泛型不一致行为?,java,generics,Java,Generics,为什么第一个方法编译,第二个不编译?Set和ImmutableSet.Builder的泛型相同,它们的add方法的类型签名也相同 import java.util.Set; import java.util.HashSet; import com.google.common.collect.ImmutableSet; public class F { public static ImmutableSet<? extends Number> testImmutableSet
Set
和ImmutableSet.Builder
的泛型相同,它们的add
方法的类型签名也相同
import java.util.Set;
import java.util.HashSet;
import com.google.common.collect.ImmutableSet;
public class F {
public static ImmutableSet<? extends Number> testImmutableSetBuilder() {
ImmutableSet.Builder<? extends Number> builder = ImmutableSet.builder();
Number n = Integer.valueOf(4);
builder.add(n);
return builder.build();
}
public static Set<? extends Number> testJavaSet() {
Set<? extends Number> builder = new HashSet<Number>();
Number n = Integer.valueOf(4);
builder.add(n);
return builder;
}
}
我想我开始找到答案了
ImmutableSet.Builder
方法add
重载,有一个备用签名add(E…元素)
。我在生成的.class文件上运行了javap-v
,我发现这个替代方法实际上就是被调用的方法。varargs元素
实际上是一个隐藏的Java数组,Java数组是协变的。也就是说,关于这个具体的例子,我们称之为
builder.add(n);
Number n
被转换为类型为Number[]
的单个元素数组。但是当你说Set时,我不知道这个数组是如何合法地转换成数组的。确实add(E)
不适用,但是对于方法add(E…)
,事情就不那么清楚了:
Java语言规范:
当且仅当以下所有条件成立时,方法m才是适用的变量算术方法:
- 一≤ i
- 如果k≥ n、 然后是n≤ 我≤ k、 ei的类型Ai可以通过方法调用转换转换为Sn的组件类型
-
在我们的例子中,Sn是捕获-?扩展数字[]
现在Sn的组件类型是什么?不幸的是,JLS没有给出该术语的正式定义。特别是,它没有明确说明组件类型是编译时类型(不需要可修改)还是运行时类型(必须可修改)。对于前者,组件类型将是capture或-?扩展数字
,它不能通过方法调用转换接受数字。对于后者,组件类型将是Number
,这显然可以
eclipse编译器的实现者似乎使用了前一个定义,而javac的实现者使用了后一个定义
两个事实似乎暗示组件类型确实是运行时类型。首先,规范:
如果所分配的值的类型与组件类型不兼容(§5.2),则会引发ArrayStoreException
如果数组的组件类型不可修改(§4.7),Java虚拟机将无法执行上一段中描述的存储检查。这就是为什么禁止使用不可归元素类型的数组创建表达式(§15.10)。可以声明数组类型的变量,其元素类型是不可重定义的,但将数组创建表达式的结果赋值给该变量必然会导致未经检查的警告(§5.1.9)
第二,针对变量arity方法的方法调用表达式的运行时评估定义如下:
如果用k调用m≠ n个实际参数表达式,或者,如果正在使用k=n个实际参数表达式调用m,并且第k个参数表达式的类型与T[]的赋值不兼容,则参数列表(e1,…,en-1,en,…,ek)的计算方式与写入的方式相同(e1,…,en-1,new | T[]{en,…,ek}),其中| T[]|表示擦除T[]的第4.6条
随意阅读这一段可能会让人认为表达式不仅是经过计算的,而且也是通过这种方式进行类型检查的,但规范并没有这么说
另一方面,将擦除用于编译时类型检查是一个非常奇怪的概念(尽管规范在其他一些情况下需要这样做)
总而言之,eclipse编译器和javac之间观察到的差异似乎源于对不可修改变量arity方法参数的类型检查规则的解释略有不同。有一种称为GET&PUT原则的方法。建议始终遵循它
获取与放置原则
仅从结构中获取值时使用扩展通配符,仅将值放入结构时使用超级通配符,同时获取和放入值时不使用通配符这两种方法都无法在我的电脑上按预期编译。请看这里,我相信这回答了您的问题:@Rohit,您使用的SDK版本是什么?这个问题可能已经在这里有了答案:这篇文章的顶部肯定是错误的。我该如何摆脱它或否决它?不幸的是,Eclipse在这种情况下不同意JDK 7,并且未能编译这两种方法。问题不在于如何修复要编译的代码。问题在于javac编译器的奇怪行为
builder.add(n);