为什么我需要显式转换泛型调用?

时间:2013-03-07 07:09:18

标签: java generics

假设我有以下内容:

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 个答案:

答案 0 :(得分: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>返回。强迫你投入演员是一种说法:“我不能担保这项操作的安全性,这是你的话,这是正确的。”

任何时候编译器都无法确认操作是否属于类型安全,它将强制您进行投射并因此产生“未经检查”的警告,以便它完成其让您知道该问题的工作。

答案 1 :(得分:4)

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

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

因此,您返回的结果是List<? extends Widget>类型(其中? extends Widgetcapture#1),这与List<T extends Widget>不兼容,因此编译器正确生成错误。

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

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

答案 2 :(得分:3)

这是因为n.getClass()始终返回Class<?>而不是Class<T>,因此编译器无法直接解析此问题。你总是需要在这里明确地施放。例如

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

答案 3 :(得分:1)

泛型及其各自的最终类型是在编译时确定的,而不是在运行时确定的。在运行时,所有通用信息都将被删除 - 这称为Type erasure。 看来你在这里试图确定函数调用时的泛型类型,这在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来声明整个类的泛型,以使它们相同。