Со-, противоположность и инвариантность в Java
Речь идет о ко-, противоположности и инвариантности. Ковариация говорит нам о том, что мы можем вынести, Контравариантность говорит нам о том, что мы можем добавить, а Инвариантность говорит нам об обоих.
Инвариантность
List<Animal>
- это инвариант . Вы можете добавить любое животное, и вы гарантированно получите любое животное вне - get(int)
дает нам Animal
, а add(Animal)
должен принять любое животное. Мы можем поместить Animal, мы получим Animal.
List<Animal> animals = new ArrayList<Dog>()
- это ошибка компилятора , поскольку он не принимает Animal
или Cat
. get(int)
по-прежнему дает нам только животных (в конце концов, собаки - это животные), но отказ от других - это нарушение сделки.
List<Animal> animals = new ArrayList<Object>()
также нарушает условия сделки. Да, он принимает любое животное (мы можем поместить туда животных), но он дает нам объекты.
Контравариантность
List<? super Dog>
это контравариант . Мы можем только поместить собак, ничего не говорится о том, что мы получаем. Таким образом, мы получаем Object.
List<? super Dog> dogs = new ArrayList<Animal>();
это работает, потому что мы можем поместить в него Dog. А животные - это объекты, поэтому мы можем извлекать объекты.
List<? super Dog> dogs = new ArrayList<Animal>();
// dogs.add(new Animal()); // compile error, need to put Dog in
dogs.add(new Dog());
Object obj = dogs.get(0);
// Dog dog = dogs.get(0); // compile error, can only take Object out
Ковариация
List<? extends Animal>
равно ковариантность . Вы гарантированно получите животное из .
List<? extends Animal> animals = new ArrayList<Cat>();
работает, потому что кошки - это животные, а get(n)
дает вам животных. Конечно, все они кошки, но кошки - это животные, так что все работает нормально.
Однако добавить что-то сложнее, поскольку у вас на самом деле нет типа, который можно было бы вставить:
List<? extends Animal> animals = new ArrayList<Cat>();
//animals.add(new Cat()); // compile error
//animals.add(new Animal()); // compile error
Animal animal = animals.get(0);
List<? extends Cat> cats = new ArrayList<Animal>();
- это ошибка компилятора , потому что вы можете убрать любое животное, но вам нужно, чтобы единственное, что можно было вынуть, - это кошки.
Ваш код
static List<? extends Animal> foo() {
List<Dog> dogs = new ArrayList<>();
return dogs;
}
Здесь все нормально. foo()
- это список, в котором вы можете убрать животных. Конечно, Поскольку собаки - животные, и вы можете убивать собак, вы можете убивать животных. Все, что вы выберете из Списка, гарантированно будет Животным.
List<Animal> dogs = Main.foo(); // compile error
Вы говорите, что dogs
- это Список, в который вы можете вставить любое Animal
, и вы гарантированно получите животных. Последняя часть проста, да, вы гарантированно получите животных, это то, что означает ? extends Animal
. Но вы не можете поместить туда произвольных Животных. Вот почему это не удается.