【发布时间】:2016-11-24 12:35:04
【问题描述】:
升级到 Java 1.8 后,我遇到了泛型方法的问题,Java 1.6 和 1.7 都可以 考虑以下代码:
public class ExtraSortList<E> extends ArrayList<E> {
ExtraSortList(E... elements) {
super(Arrays.asList(elements));
}
public List<E> sortedCopy(Comparator<? super E> c) {
List<E> sorted = new ArrayList<E>(this);
Collections.sort(sorted, c);
return sorted;
}
public static void main(String[] args) {
ExtraSortList<String> stringList = new ExtraSortList<>("foo", "bar");
Comparator<? super String> compGen = null;
String firstGen = stringList.sortedCopy(compGen).get(0); // works fine
Comparator compRaw = null;
String firstRaw = stringList.sortedCopy(compRaw).get(0); // compiler ERROR: Type mismatch: cannot convert from Object to String
}
}
我使用 Oracle javac (1.8.0_92) 和 Eclipse JDT (4.6.1) 编译器尝试了这个。两者的结果相同。 (报错信息有点不同,但本质上是一样的)
除此之外,可以通过避免原始类型来防止错误,这让我感到困惑,因为我不明白其中的原因。
为什么 sortedCopy-Method 的 raw 方法参数对返回值的泛型类型有影响?泛型类型已在类级别定义。该方法没有定义单独的泛型类型。引用 list 被键入到 <String>,返回的 List 也是如此。
为什么 Java 8 会在返回值上从类中丢弃泛型类型?
编辑:如果 sortedCopy 的方法签名被更改(如 biziclop 所指出)为
public List<E> sortedCopy(Comparator c) {
那么编译器确实会考虑来自类型ExtraSortList<E> 的泛型类型E,并且不会出现错误。但是现在参数c 是原始类型,因此编译器无法验证提供的 Comparator 的泛型类型。
编辑:我对 Java 语言规范进行了一些审查,现在我想,我是否缺乏理解,或者这是编译器中的一个缺陷。因为:
-
泛型类型
E的Scope of a Declaration 是类ExtraSortList,其中包括方法sortedCopy。 -
sortedCopy方法本身没有声明一个泛型类型变量,它只是从类作用域引用类型变量E。请参阅 JLS 中的Generic Methods - JLS 也在同一部分中声明
在调用泛型方法时,可能不需要显式提供类型参数,因为它们通常可以被推断(第 18 节(类型推断))。
- 引用
stringList是用String定义的,因此编译器不需要在调用sortedCopy时推断E的类型,因为它已经定义了。 - 因为
stringList已经具有E的具体类型,所以对于给定的调用,参数c应该是Comparator<? super String>。 - 返回类型也应该使用已经具体化的类型
E,因此应该是List<String>。
这是我目前对 Java 编译器应该如何评估调用的理解。如果我错了,解释为什么我的假设是错误的会很好。
【问题讨论】:
-
“我的错误在哪里?”在使用原始类型时。您知道正确的做法,就像上面的示例一样 - 在
Comparator之后添加<String>。 -
实际上对我来说都很好。 (1.8.0_45)
-
@KevinEsche 和我一样!
-
在实践中,“不使用原始类型”当然是一个足够的答案,但仍然是一个有趣的问题,即类型推断规则发生了什么变化导致它变得无效。 (如果它根本无效。)