0

我已经阅读了应该阻止您将 a 添加到 a的泛型 get 和 put 规则BananaList<? 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

我这样做是否违反了任何合同?还是我在规避保护并且不应该这样做?

4

3 回答 3

2

Your last code is just fine, but the same code would have worked using just a List<Fruit>. The difference is that a List<? extends Fruit> can be a List whose generic type is any sort of fruit, such as Apple, and you know that calling get() on a List<Apple> will give you an Apple.

It's perfectly acceptable to add an Apple to a List<Fruit>. The difference is that when you pull the object back out of the List, you only know that it's a Fruit, not what specific kind of fruit it is.

于 2014-04-01T13:15:18.367 回答
2

No, it's absolutely fine to have two different kinds of Fruit in a List<Fruit>.

The issue comes when you've actually created a List<Banana>. For example:

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!
于 2014-04-01T13:15:39.530 回答
1

Consider the case where your list is a function parameter, not a local variable:

public void eatFruit(List<? extends Fruit> list)
{
    for (Fruit fruit : list)
    {
        eat(fruit);
    }
}

You can pass a List<Banana> or a List<Apple> to this method, or even a List<Cherry>. The compiler knows only that the "?" in "? extends Fruit" represents some class that extends Fruit, but not what that class is. It has no way of knowing whether a Banana is an instance of that class, so it won't let you add one to the list. It will let you read the elements of the list and use them as fruit, as I did.

If you define the list as simply "List<Fruit> list", then it would allow you to add a Banana to the list, or any other type of fruit. But it would not allow you to call the function and pass in a List<Banana>, because a List<Banana> can only contain Bananas, but your function could add any type of fruit.

The whole point of Generics is that if you create a List<Cherry> you should never be able to add a Banana to it (unless you cast it to a raw List, defeating the purpose). The compiler knows that your list contains only fruit of a certain type (possibly any type), but it does not know what type that is. So it won't let you add anything to it. This is the only way to ensure that the list remains pure.

于 2014-04-01T13:44:26.337 回答