克服泛型put-get规则

时间:2014-04-01 13:12:03

标签: java generics generic-list pecs

我已阅读有关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

这样做是否违反任何合同?或者我是在规避保护而不应该做的呢?

3 个答案:

答案 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;你应该永远不能添加一个香蕉(除非你把它投入原始列表,打败目的)。编译器知道你的列表只包含某种类型的果实(可能是任何类型),但它不知道它是什么类型。所以它不会让你添加任何东西。这是确保列表保持纯净的唯一方法。