【问题标题】:Understanding wildcard-constraint <? super T>理解通配符约束 <?超级T>
【发布时间】:2015-06-25 04:16:55
【问题描述】:

我可以使用基本的泛型表达式,但通配符约束让我很困惑。

信息:学生扩展人,人扩展动物

 List<? super Animal> aList= new ArrayList<>();

       // does not compile as expected, 
       // since the list is restricted to something that has Animal as superclass.
       // aList.add(new Object()); 

    aList.add(new Animal()); // I can add animal
    aList.add(new Person()); // I can add Person
    aList.add(new Student()); // I can add Student

    Animal a = new Animal();
    Animal b = new Animal();
    Person p = new Person();
    Student s = new Student();
    // test
    a = b; //I can assign an animal to another animal
    a = p; // I can assign a person to an animal
    a = s; // I can assign a student to an animal

    Animal animal = aList.get(0); // DOES NOT COMPILE, WHY ?

问题:我不明白为什么最后一个作业不起作用。上面的例子表明,该列表中的任何东西都肯定是动物,但我无法从该列表中获取动物。

更具体:当我知道我只能添加以 Animal 作为超类的类型时,为什么我不能期望从 Animal 类型中删除对象?

事实:我只能添加扩展 Animal 的对象!我只是尝试添加一个汽车对象,但它不起作用!

我开始怀疑我的理智了,希望你能帮助我。谢谢你

另外:

Object animal = aList.get(0); // works

为什么即使我知道我不能添加 Object-Type,这个语句仍然有效?

解决方案:(基于接受的答案)

我误解了&lt;? super Animal&gt;的意思

我认为这是什么意思:任何将 Animal 作为超类的类。

它(显然)是什么意思:任何作为动物超类的类。

因此,List 也可能包含 Object 类型的对象,这就是 Animal animal = aList.get(0); 失败的原因。

干杯

【问题讨论】:

  • 我想你只是想要: List 在你的 aList 声明中
  • Animal animal = aList.get(1) 怎么样(以你的例子)?如果你能回答这个问题,那么你就有理由不被允许。
  • 您确定您正确区分了superextendsstackoverflow.com/questions/4343202/…
  • List&lt;? super Animal&gt; not 的意思是:“以 Animal 作为超类的东西”,它的意思是:某个未知类型,它是 Animal 的超类。因此,与您在评论中所说的相反。
  • @Seelenvirtuose:人是人,人是哺乳动物,哺乳动物是动物。或:(人->人类)&&(人类->哺乳动物)&&(哺乳动物->动物)人->动物。 (当然除了那些用裂口雕刻的粘土制成的。)

标签: java generics


【解决方案1】:

这里的其他答案是对的,但表述不够清楚,这就是混乱的来源。

A List&lt;? extends Animal&gt; 不是的意思是“所有事物都扩展 Animal 的列表”。它的意思是“某种类型的列表T,我不会告诉你它是什么,但我知道T 扩展了Animal。” (在类型理论中,这些被称为存在类型——存在一个类型T,我们的ListList&lt;T&gt;,但我们不一定知道T 是什么.)

区别很重要。如果您有“所有扩展 Animal 的事物的列表”,那么将 Dog 添加到列表中是安全的——但这不是通配符所表达的。 List&lt;? extends Animal&gt; 的意思是“某事物的列表,该事物扩展了 Animal”。可能是List&lt;Animal&gt;,或者List&lt;Dog&gt;,或者List&lt;Cat&gt;——我们不知道。所以我们没有理由认为将 Dog 添加到该列表中是安全的——因为也许我的列表是List&lt;Cat&gt;

在您的示例中,您有一个List&lt;? super Animal&gt;。这意味着,某个类型 T 的列表,其中 T 是 Animal 的超类型之一。它可能是List&lt;Object&gt;,或List&lt;Animal&gt;,或者如果Animal 有超类型HasLegs,它可能是List&lt;HasLegs&gt;。在这里,将 Dog 放入此列表是安全的——因为无论它是什么列表,Dog 绝对是其中之一(动物、对象等),但是当您取出某些东西时 列表中,你不知道它是狗、动物还是只是一个对象。

【讨论】:

  • 我仍然不明白为什么我不能将 Object 类型的东西添加到列表中,但另一方面我可以从列表中获取它?我想我对此太愚蠢了。它就像一袋不同颜色的弹珠,我知道我无法正确选择颜色,但我知道里面的所有东西至少是球形的。
  • 当你有一个List&lt;? extends Animal&gt;(可以是List、List、List)时,你可以把动物带出去,但你可以't 把动物放进去。当你有一个List&lt;? super Animal&gt;(可以是List,List)时,你可以把动物放进去,但你不能把它们拿出来。只有当你有一个不变的列表——List——你才能把动物拿出来放进去。
  • @SklogW 可能是因为您仍然错误地解释了通配符。 List&lt;? extends Animal&gt; 并不意味着“所有事物都扩展了 Animal 的列表”。正是这种普遍的直觉导致人们陷入混乱;一旦你摆脱这种直觉并意识到通配符的实际含义,它就会变得更有意义......
  • 我想我明白了。感谢您的努力。
【解决方案2】:

List&lt;? super Animal&gt; aList是引用,可以用来处理List&lt;Animal&gt;,也可以是Animal的任何超类,例如List&lt;Object&gt;

换句话说List&lt;? super Animal&gt; aList
- 不是对可以存储任何动物超类型的列表的引用。
+ 它引用一些特定类型的列表,它是Animal(包括Animal本身)的超类型,但你不知道是哪个准确输入。

可能是这样的

List<Object> someList = new ArrayList<>();
someList.add(new Car());//OK since Car is a Object
List<? super Animal> aList = someList;// OK since Object is supertype of Animal

因此,代码

Animal a = aList.get(0);//and where did that Car come from?

不安全。存储get(0) 的结果的唯一安全类型是Object,所以您需要

Object o = aList.get(0);

或者如果您想确保get 将返回Animal,请将您的aList 引用类型更改为List&lt;Animal&gt; 甚至List&lt;? extends Animal&gt;

【讨论】:

  • 我刚试过。如果我创建一个 Car 类并尝试将 Car 添加到列表中,那么它不起作用。它不兼容
  • @SklogW 我添加了一个示例,展示了如何最终将 Car 放在 Animal 参考中。
  • 所以这仅仅是因为Java允许它分配一个受限制的列表给一个对象列表吗?现在为什么我可能期望一个 Car 对象但 java 允许这样做是有道理的。
  • 什么意思?让List&lt;? super Animal&gt; 引用特定列表,如List&lt;Animal&gt;List&lt;LeavingThing&gt; 甚至List&lt;Object&gt;&lt;? super Animal&gt; 的全部目的,因此简单地假设List&lt;LeavingThing&gt;List&lt;Object&gt; 将包含只有Animals
【解决方案3】:

List&lt;? super Animal&gt; 表示Animal 的任何超类的列表。如果AnimalLivingThing 的子类,则List&lt;? super Animal&gt; 可以是Animals 的列表、LivingThings 的列表或Object 的列表。在最后两种情况下,分配 Animal x = aList.get(0) 当然是非法的,因为您不能将 LivingThing 分配给 Animal 类型的引用。

考虑以下方法(它不会编译):

void f(List<? super Animal> list) {
    Animal a = list.get(0); // line 1
    list.add(new Object()); // line 2
}

方法调用

List<Object> list = new ArrayList<>();
list.add(new Object());
f(list);

是正确的,因为ObjectAnimal 的超类。但是,如果 f 方法可以编译,您将尝试在第 1 行中将 Object 分配给 Animal 引用,因此它不是类型安全的。

另一方面,考虑方法调用:

f(new List<Animal>());

如果方法f 可以编译,我们现在将添加一个Object 实例到一个应该只包含Animals 的列表中。

这就是不允许第 1 行和第 2 行的原因。如果你有课

public class A<T> {
    public put(T in) { } 
    public T get() { } 
}

那么对于A&lt;? super Animal&gt; 类型,get 方法将返回Object(不是Animal),而put 方法需要Animal 类型的参数。

【讨论】:

  • 那为什么我不能添加动物超类的对象呢?我只能添加以 Animal 作为超类的对象。
  • @SklogW 您不能添加Animal 超类的对象,因为它也可能是Animals 的列表,您无法向其中添加此类对象。
猜你喜欢
  • 2012-08-30
  • 2011-01-15
  • 2023-03-03
  • 2021-05-21
  • 1970-01-01
  • 1970-01-01
  • 2011-02-27
  • 2012-02-02
  • 1970-01-01
相关资源
最近更新 更多