13

假设我有以下内容:

public <T extends Widget> List<T> first(T n) {
    return first(n.getClass());
}
public <T extends Widget> List<T> first(Class<T> n) {
    return new ArrayList<>();
}

编译器在第 3 行用“ incompatible types; required: java.util.List<T>; found: java.util.List<capture#1 of ? extends my.app.Widget>”抱怨。我不明白为什么。T在我看来,除了子类型之外,类型在任何一种情况下都不会改变,这似乎是合理的。

这可以通过显式转换来解决,尽管我不知道为什么需要它。

public <T extends Widget> List<T> first(T n) {
    return (List<T>)first(n.getClass());
}
public <T extends Widget> List<T> first(Class<T> n) {
    return new ArrayList<>();
}

这可能是编译器错误吗?

注意我使用的是 JDK 1.7.0_15:

java version "1.7.0_15"
Java(TM) SE Runtime Environment (build 1.7.0_15-b03)
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)
4

4 回答 4

7

正是由于您所说的原因,类型参数实际上可能是您传入的对象的实际运行时类型的超类型! T#getClass()不返回Class<T>,它返回Class<? extends T>

Number number = Integer.valueOf(1);
List<Number> list = first(number);

当您n.getClass()在运行时调用时将返回Integer.class,不是Number.class,但您正试图将结果分配给List<Number>!编译器无法知道真正的运行时类型是什么,它最多只知道List<? extends Number>返回的类型。强迫你加入演员阵容是它说“我不能保证这次手术的安全性,你保证它是正确的”的方式。

任何时候编译器不可能确认一个操作是类型安全的,它会强制你强制转换,从而引发一个“未经检查”的警告,这样它就完成了让你知道问题的工作。

于 2013-03-07T07:31:59.467 回答
4

getClass()返回一个类型的值Class<?>。当您将它传递给 的另一个覆盖时first,您正在执行未经检查的强制转换为Class<? extends Widget>. 它没有被检查,因为泛型在运行时使用“类型擦除”,这意味着 JVM 无法检查它;通过-Xlint:unchecked查看此警告。

请注意,这不是Class<T extends Widget>,这意味着类型系统不确保此方法的类型参数(的第一个覆盖first)与被调用的方法(另一个first)的类型参数相同。

所以你得到的结果是类型List<? extends Widget>(where ? extends Widgetis capture#1),它与 不兼容List<T extends Widget>,因此编译器正确地产生了错误。

在这种情况下,您碰巧知道(尽管编译器不知道)这是一件合理的事情,因此您可以使用显式强制转换来覆盖它。但在那种情况下,为什么不把这个方法做成这样:

public <T extends Widget> List<T> first() {
    return new ArrayList<T>();
}
于 2013-03-07T07:30:00.273 回答
3

这是因为n.getClass()总是返回Class<?>而不是Class<T>因此编译器无法直接解决这个问题。你总是需要在这里显式地转换。例如

public <T extends Widget> List<T> first(T n) {
    return first((Class<T>)n.getClass());
}
于 2013-03-07T07:35:44.107 回答
1

泛型及其各自的最终类型是在编译时确定的,而不是在运行时确定的。在运行时,所有通用信息都会被擦除——这被称为类型擦除。似乎在这里您试图确定函数调用时的泛型类型,Java 不支持这种类型

public <T extends Widget> List<T> first(T n) {
    return first(n.getClass());
}

在这里,您需要返回List<T>- 一个列表实例。一个具体的实例必须有一个具体的参数T。即一个人不能返回List<T extends Widget>- 类型将是未知的。你会在这样的列表中存储什么?

public <T extends Widget> List<T> first(Class<T> n) {
    return new ArrayList<>();
}

在这里,您希望根据提供的类返回一个列表 - 但请注意您实际上正在创建一个通用 ArrayList,然后将其转换为所需的类型。问题是这里的 T 和前面方法中的 T 不一样。您需要用一个 T 声明整个类的泛型,以使它们相同。

于 2013-03-07T07:31:41.103 回答