Преодоление дженериковых паттернов
Я читал об общих правилах get и put, которые должны помешать вам добавить 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 ответа
Нет, совершенно нормально иметь два разных вида 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!
Ваш последний код просто отлично, но тот же код работал бы, используя только List<Fruit>
, Разница в том, что List<? extends Fruit>
может быть List
чей родовой тип - это любой фрукт, такой как Apple
и вы знаете, что зовет get()
на List<Apple>
даст вам Apple
,
Вполне приемлемо добавить Apple
к List<Fruit>
, Разница в том, что когда вы вытаскиваете объект из List
Знаешь только, что это Fruit
, а не какой конкретно вид фруктов это.
Рассмотрим случай, когда ваш список является параметром функции, а не локальной переменной:
public void eatFruit(List<? extends Fruit> list)
{
for (Fruit fruit : list)
{
eat(fruit);
}
}
Вы можете передать List
Если вы определите список как просто "Список
Весь смысл Generics в том, что если вы создаете List