难以理解通配符

3
public static void reverse(List<?> list) 
{ 
List<Object> tmp = new ArrayList<Object>(list);
for (int i = 0; i < list.size(); i++) 
{

list.set(i, tmp.get(list.size()-i-1)); // compile-time error , why ? 

}

}

我正在学习泛型。我知道当使用通配符 ? 时,它会被替换为适当的类型。当调用 reverse() 方法时,? 将得到替换,由于每种类型都是 Object 的子类型,因此不应该出现错误。我需要一份清晰易懂的解释,请帮忙。


1
List<?> 是一个无界通配符。这意味着你只能将 null 插入到列表中。 - Tim Biegeleisen
https://docs.oracle.com/javase/tutorial/java/generics/unboundedWildcards.html - Tim Biegeleisen
2
你误解了 ?。它不会被替换,而是用来标识一个未知量 - Andreas
3个回答

1
您可以将任何 List<SomeType> 传递给您的 reverse 方法作为 List<?> list 参数,编译器应该只允许您向该 List 添加 SomeType 类型的元素。

例如,它可以是 List<String>List<Integer> 等等...

因此,list.set 是无法工作的,因为编译器不知道实际传递给方法的 List<?> list 支持的元素类型。它不知道 List<Object> tmp 的元素来自同一个 list(因此可以安全地添加到它)。

编写方法的正确方式是定义一个泛型类型参数:

public static <T> void reverse(List<T> list) 
{ 
    List<T> tmp = new ArrayList<> (list);
    for (int i = 0; i < list.size(); i++)  {
        list.set(i, tmp.get(list.size()-i-1));
    }
}

现在编译器知道listtmp都包含相同类型的元素。

Box<?> box = new Box<>("hello"); 和 Box<?> box = new Box<String>("hello") 的区别是什么? 其中 Box 是一个类 Box<T>,具体实现如下: class Box<T> { T t; Box(T t) { this.t = t; } } - cs-dev
@steve 我认为前者将会创建一个 Box<Object> (因为new Box<>的泛型参数类型无法从 box 变量的类型推导出来),后者则创建一个 Box<String> - Eran

0

泛型以这种方式工作

在这里:

public static void reverse(List<?> list) 

由于我们不知道list的元素类型代表什么,所以我们无法向其添加对象。
这里:
List<Object> tmp = new ArrayList<Object>(list);
  ...
list.set(i, tmp.get(list.size()-i-1)); // compile-time error , why ? 

你可以在一个 List 中添加不同类型的元素,其中包括 Object
但在你的情况下,你写的是:

List<Object> tmp = new ArrayList<Object>(list);

但是编译器不会试图理解你代码的逻辑而产生异常。 否则它就需要检查整个代码,编译器的错误信息可能会变得非常复杂。 举个例子,假设你在代码中同时执行了add tmp.set(0, "aa");

0

List<?> 是一个未知类型的列表,我们不知道这个列表中的类型是什么,泛型的引入是为了消除运行时类型转换异常并提供安全性。您不能向包含未定义类型的集合添加任何类型,因为这样会破坏它。例如,如果允许这样做:

    List<Dog> dogs = new ArrayList<>(Arrays.asList(new Dog("dog")));

    public doSomething(List<?> notKnownType) {
        notKnownType.add(new Cat("cat"));   
// if this ok or not? <?> is not known type, all types have Object as parent let's add Cat
    }

    for (Dog dog : dogs) {
        System.out.println(dog); //ClassCastException
    }

ClassCastException,因为我们将猫添加到狗中,并将每只狗都强制转换为猫,在运行时泛型不存在,所有的集合等都包含Object。编译后,这一行将变成这样:

for (Object dog : dogs) {
    System.out.println((Dog) dog); //ClassCastExceptin 
}

如果不允许这样做,您将无法向未知类型的集合中添加任何内容,并在此处获得编译时错误:list.set(i, tmp.get(list.size()-i-1)); // 编译时错误,为什么?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接