我已阅读有关generics get and put rule的信息,这会阻止您向Banana
添加List<? extends Fruit>
:
public abstract class Fruit { }
public class Banana extends Fruit { }
public class Apple extends Fruit { }
List<? extends Fruit> lst = new ArrayList<>();
lst.add(new Banana()); // Compile error "The method add(capture#6-of ? extends Fruit) in the type List<capture#6-of ? extends Fruit> is not applicable for the arguments (Banana)"
据我所知,如果你没有编译错误,你可以这样做:
List<? extends Fruit> lst = new ArrayList<>();
lst.add(new Banana());
lst.add(new Apple());
这听起来不对,因为你在同一个列表中得到了不同的对象类型(我看到了@Tom Hawtins解释原因的答案,我相信他:)(我找不到它的链接))。
但是,如果您实施以下内容:
public class ListFruit implements List<Fruit> {
List<Fruit> list;
public ListFruit() {
list = new ArrayList<>();
}
@Override public int size() { return list.size(); }
// All delegates of "list"
@Override public List<Fruit> subList(int fromIndex, int toIndex) { return list.subList(fromIndex, toIndex); }
}
然后执行:
ListFruit lst = new ListFruit();
lst.add(new Banana());
lst.add(new Apple());
for (Fruit fruit : lst)
System.out.println(fruit.getClass().getName());
没有编译错误,以及以下(预期)输出:
Banana
Apple
这样做是否违反任何合同?或者我是在规避保护而不应该做的呢?
答案 0 :(得分:2)
你的上一个代码很好,但是使用List<Fruit>
代码就可以使用相同的代码。区别在于List<? extends Fruit>
可以是List
,其泛型类型是任何类型的水果,例如Apple
,并且您知道在{{1}上调用get()
会给你一个List<Apple>
。
向Apple
添加Apple
是完全可以接受的。不同之处在于,当您将对象拉回List<Fruit>
时,您只知道它是List
,而不是它的特定种类。
答案 1 :(得分:2)
不,在Fruit
中有两种不同的List<Fruit>
是绝对正常的。
当实际创建List<Banana>
时会出现问题。例如:
List<Banana> bananas = new ArrayList<>();
bananas.add(new Banana());
// This is fine!
List<? extends Fruit> fruits = bananas;
// Calling fruits.get(0) is fine, as it will return a Banana reference, which
// is compatible with a Fruit reference...
// This would *not* be fine
List<Fruit> badFruits = bananas;
badFruits.add(new Apple());
Banana banana = bananas.get(0); // Eek! It's an apple!
答案 2 :(得分:0)
考虑您的列表是函数参数而不是局部变量的情况:
public void eatFruit(List<? extends Fruit> list)
{
for (Fruit fruit : list)
{
eat(fruit);
}
}
您可以传递List&lt; Banana&gt;或列表&lt; Apple&gt;对于这种方法,甚至是List&lt; Cherry&gt;。编译器只知道“?”在“?extends Fruit”中代表了一些扩展Fruit的类,但不是那个类。它无法知道Banana是否是该类的实例,所以它不会让你在列表中添加一个。它会让你阅读列表中的元素,并像我一样将它们用作水果。
如果您将列表定义为“List&lt; Fruit&gt; list”,那么它将允许您将香蕉添加到列表或任何其他类型的水果中。但它不允许你调用函数并传入List&lt; Banana&gt;,因为List&lt; Banana&gt;只能包含香蕉,但你的功能可以添加任何类型的水果。
泛型的全部意义在于,如果你创建一个List&lt; Cherry&gt;你应该永远不能添加一个香蕉(除非你把它投入原始列表,打败目的)。编译器知道你的列表只包含某种类型的果实(可能是任何类型),但它不知道它是什么类型。所以它不会让你添加任何东西。这是确保列表保持纯净的唯一方法。