Ко-, контра- и инвариантность в Java
Речь идет о Со-, Контра- и Инвариантности. Ковариантность говорит нам о том, что мы можем убрать, контравариантность — о том, что мы можем добавить, а инвариантность — о том и другом.
Инвариантность
List<Animal>
является инвариантным. Вы можете добавить любое Животное, и вы гарантированно получите любое Животное — get(int)
дает нам Animal
, а add(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>();
это работает, потому что мы можем поместить в него собаку. А Животные — это Объекты, поэтому мы можем доставать объекты.
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
. Но вы не можете вставлять произвольные Животные. Вот почему это не работает.
person
Polygnome
schedule
04.08.2020