【问题标题】:I not understend wildcards in java [duplicate]我不理解java中的通配符[重复]
【发布时间】:2018-08-05 07:19:29
【问题描述】:

我有课:

class Animal{
    public void type(){
        System.out.println("I am Animal");
    }
}
class Dog extends Animal{
    public void type(){
        System.out.println("I am Dog");
    }
}
class Cat extends Animal{
    public void type(){
        System.out.println("I am Cat");
    }
}
class Haski extends Dog{
    public void type(){
        System.out.println("I am Haski");
    }
}

然后我用wildcards 创建List

List<? extends Animal> animalList = new ArrayList<Animal>();

我知道我不能向animalList 添加一些对象。我在不同的书籍、互联网上的文章、视频课程中读到过它,但我仍然不明白为什么?如果我们认为animalList 只包含对象extends Animal,为什么java 不能添加任何objects extends Animal 并将其转换为Animal

animalList.add(new Dog()); //cast dog to Animal
animalList.add(new Cat()); //cast cat to Animal

编译器有足够的信息 - objects extends Animal 为什么它不能转换?

编辑:

所以我没有正确理解。

List<Animal> animalList1 = new ArrayList<Animal>();
animalList.add(new Animal());
animalList.add(new Dog());
animalList.add(new Cat());
animalList.add(new Haski());

List<? extends Animal> animalList

有一种感觉,它们应该是一样的。但我感觉不到原则上的区别

【问题讨论】:

  • List&lt;? extends Animal&gt; animalList 不需要只保留ArrayList&lt;Animal&gt;();。它还可以包含List&lt;Dog&gt;(旨在仅包含Dogs 或其子类型的列表)所以您是否希望编译器允许您将Cat 对象添加到此类列表中?
  • ? extends Animal 视为扩展Animal 的一个非常具体(但未知)的类。出现您的理解问题是因为您认为这意味着任何扩展 Animal 的类。
  • List&lt;Animal&gt;List&lt;? extends Animal&gt; 不是同一类型。您可以将List&lt;Dog&gt; 分配给List&lt;? extends Animal&gt; 变量,但不能将其分配给List&lt;Animal&gt; 变量。

标签: java generics wildcard


【解决方案1】:

因为这个:

List<Dog> dogList = new ArrayList<>(); // compiles
List<? extends Animal> listOfUnknownAnimalType = dogList; // compiles
listOfUnknownAnimalType.add(new Cat()); // doesn't compile

如果编译第三行,您将能够将 Cat 添加到 List&lt;Dog&gt;,这将完全破坏泛型带来的类型安全:listOfUnknownAnimalType 在第 2 行使用 dogList 初始化。所以两个变量都引用同一个列表,即List&lt;Dog&gt;。因此,如果您将猫添加到 listOfUnknownAnimalType,则将其添加到 dogList。将 Cat 添加到狗列表中是不对的:List&lt;Dog&gt; 应该只包含狗,而不是猫。

想象一下你有这个方法:

public void printAllTypes(List<Animal> list) {
    list.forEach(a -> System.out.println(a.type()));
}

这很好,效果很好:

List<Animal> list = new ArrayList<>();
list.add(new Cat());
list.add(new Dog());
printAllTypes(list);

现在假设你有这个:

List<Dog> list = new ArrayList<>();
list.add(new Haski());
list.add(new Dog());
printAllTypes(list);

最后一行无法编译,因为List&lt;Dog&gt; 不是List&lt;Animal&gt;(原因同上)。这就是通配符变得有用的地方:因为您的方法实际上并不关心列表的具体泛型类型,只要它扩展 Animal,并且由于它不会改变列表,您可以将您的方法重写为

public void printAllTypes(List<? extends Animal> list) {
    list.forEach(a -> System.out.println(a.type()));
}

现在您可以使用List&lt;Animal&gt; 调用它,也可以使用List&lt;Dog&gt;List&lt;Cat&gt; 作为参数。

【讨论】:

  • 好的。当我们调用 get 方法 listOfUnknownAnimalType.get() 时,无论如何我们都会得到 Animal。即使在 2 行中,我们将狗添加到此列表中。如果我们在 3 行中添加 Cat - 如果我们在 Cat 上调用 listOfUnknownAnimalType.get() 我们得到 Animal,它将如何破坏类型安全?猫扩展动物
  • 再次阅读代码。 listOfUnknownAnimalType 被初始化为 dogList。所以两个变量都引用同一个列表,即 List。因此,如果将 Cat 添加到 listOfUnknownAnimalType,则将其添加到 dogList。将 Cat 添加到狗列表中是不对的:List 应该只包含狗,而不是猫。
  • 仅当我将列表作为方法中的参数传递时这才有用吗?还是有其他用途?
  • 是的,通配符在方法参数类型中最有用。
猜你喜欢
  • 2018-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-14
  • 1970-01-01
相关资源
最近更新 更多