Java的。集合中的通配符
即使在阅读Stack Overflow和各种教程网站中的类似帖子后,我也很难理解集合中的通配符。我在下面做了一个非常简单的例子。你能解释一下我如何在Collection< myClass >
,Collection< ? Extends MyClass >
和Collection< ? Super MyClass >
之间做出选择吗?
package z5;
import java.util.ArrayList;
public class Z5 {
public static class Animal{
}
public static class Mammal extends Animal{
}
public static class Reptile extends Animal{
}
public static class Lion extends Mammal{
}
public static class Tiger extends Mammal{
}
public static class Snake extends Reptile{
}
public static void main(String[] args) {
ArrayList<Mammal> catHouse1 = new ArrayList<Mammal>();
catHouse1.add(new Lion());
catHouse1.add(new Tiger());
catHouse1.add(new Mammal());
catHouse1.add(new Animal()); //ERROR
ArrayList<? super Mammal> catHouse2 = new ArrayList<Mammal>();
catHouse2.add(new Lion());
catHouse2.add(new Tiger());
catHouse2.add(new Mammal());
catHouse2.add(new Animal()); //ERROR
ArrayList<? extends Mammal> catHouse3 = new ArrayList<Mammal>();
catHouse3.add(new Lion()); //ERROR
catHouse3.add(new Tiger()); //ERROR
catHouse3.add(new Mammal()); //ERROR
catHouse3.add(new Animal()); //ERROR
ArrayList<Mammal> zooMammals = new ArrayList<Mammal>();
zooMammals.addAll(catHouse1);
zooMammals.addAll(catHouse2); //ERROR
zooMammals.addAll(catHouse3);
ArrayList<Animal> zooAnimals = new ArrayList<Animal>();
zooAnimals.addAll(catHouse1);
zooAnimals.addAll(catHouse2); //ERROR
zooAnimals.addAll(catHouse3);
}
}
在上面的例子中,我创建了一个类的层次结构。动物是哺乳动物和爬行动物的超类,而哺乳动物是狮子和老虎的超类。
我在动物园为三个猫屋制作了ArrayLists。第一个是简单的ArrayList&lt;哺乳动物&gt;。我可以添加任何类型的哺乳动物或其子类。
第二个是ArrayList&lt; ?超级哺乳动物&gt;。我还可以添加任何类型的哺乳动物或其子类。
第三个是ArrayList&lt; ?延伸哺乳动物&gt;。我不能添加任何东西。
最后,我将我的三个猫屋添加到动物园馆藏中。 Animal和Mammal是这里的主要超类,无论接收者ArrayList包含哪种类型,行为都是相同的。 ArrayList&lt;哺乳动物&gt;和ArrayList可以添加到动物园。 ArrayList不能。
以下是我的问题:
1)如果我想创建一个包含某个超类的所有子类的数组,为什么我需要一个通配符?我不能只声明一切ArrayList&lt;超类&gt;并获得我需要的功能?
2)我理解“&lt;?extends superclass&gt;”接受超类及其所有子类。 Ergo,&lt; ?延伸哺乳动物&gt;“接受哺乳动物,狮子和老虎。听起来就像”&lt;哺乳动物&gt;“。有什么区别?
3)我读到“&lt;?super className&gt;”接受任何className的超类。这听起来不对。在上面的例子中,狮子会不是哺乳动物的超类,但是“&lt;?super Mammal&gt;”接受狮子会。动物是哺乳动物的超类,但是“&lt;?super Mammal&gt;”不接受它。我想那里有错误的信息。
4)如果“&lt;?extends superclass&gt;”是只读的,我开始填充它的方式是什么?有一个只能读取的空列表有什么意义?
5)为什么addAll方法不适用于“&lt;?super className&gt;”?
我知道这些是基本问题,我知道他们之前已经得到了回答。我试图给出一个尽可能简单的代码示例,希望能给我一个尽可能清晰的答案。如果您有任何建议,请提前致谢。
答案 0 :(得分:3)
我不会详细介绍。
List<Animal>
表示:此列表包含Animal的实例。所以你可以添加你想要的任何动物,当你从这个列表中获取元素时你肯定会得到一个动物。
List<? extends Animal>
表示:此列表是通用列表,但我们不知道泛型类型的类型。我们所知道的是泛型类型是Animal,或者Animal的任何子类。例如,它可以是List<Animal>
,List<Reptile>
或List<Lion>
。但我们不知道。因此,从此列表中获取元素时,您可以保证获得一个Animal。但是你不会被允许在列表中添加任何内容,因为你不知道它的类型。如果您被允许添加某些内容,则可以将Lion存储在List<Reptile>
中,该列表将不再是类型安全的。
List<? super Animal>
表示:此列表是通用列表,但我们不知道泛型类型的类型。我们所知道的是,泛型类型是Animal,或者Animal的任何超类或超级接口。例如,它可以是List<Animal>
或List<Object>
。所以,当从这个列表中获取一个元素时,你可以肯定的是它是一个Object。你可以做的肯定是在这个列表中添加你想要的任何动物,因为List<Animal>
和List<Object>
都接受Animal,Lion,Reptile或Animal的任何子类。
大多数情况下,您将这些通配符用于方法的参数。
当一个方法将列表作为参数并且只对从列表中读取感兴趣时(即列表是生产者),您将使用List<? extends Animal>
。这使您的方法更具可重用性,因为它可以与List<Animal>
一起使用,也可以与List<Reptile>
,List<Lion>
等一起使用。
当一个方法将列表作为参数并且只对向列表添加元素感兴趣时(即列表是消费者),您将使用List<? super Animal>
。这使您的方法更具可重用性,因为它可以与List<Animal>
一起使用,也可以与List<Object>
一起使用。
此规则称为PECS: Procucer:Extends;消费者:超级。
如果您的方法将列表作为参数,并且必须从列表中获取元素并从中添加元素,那么您将使用List<Animal>
。
答案 1 :(得分:1)
如果您只想要一个包含Animal
或子类Animal
的列表,则应使用List<Animal>
。我认为这是你大部分时间都需要的。
<强>延伸:强>
ArrayList<? extends Mammal> list = ..
这意味着list
类型为Mammal
或其子类之一。所以你可以这样做:
ArrayList<? extends Mammal> list = new ArrayList<Tiger>(); // List of type Tiger
使用<? extends Mammal>
时,您无法在list
中插入任何内容(null
除外)。没有类型安全的方法来插入任何东西的原因。您无法将Mammal
添加到list
,因为list
可能属于Tiger
类型(如上例所示)。 Mammal
不是Tiger
,所以这不起作用。
您也无法将Tiger
添加到list
。那是因为<? extends Mammal>
没有告诉你有关Tiger
的任何信息。 list
类型Lion
也是可能的。
<强>超:强>
对于<? super Mammal>
,情况正好相反。 <? super Mammal>
表示该列表的类型为Mammal
或其超类之一。你可以这样做:
ArrayList<? super Mammal> list = new ArrayList<Animal>(); // List of type Animal
您可以将Mammal
的任何子类添加到此列表中。这是安全的,因为Mammal
的子类始终是Animal
的子类。但是,您不能使用Object
以外的其他类型从列表中获取任何内容。那是因为您不知道确切的列表类型。
Mammal m = list.get(0); // can't work because list can be a List<Animal>