Почему не могу перечислить заменить на List <Animal>? - PullRequest
5 голосов
/ 04 августа 2020

Рассмотрим следующий код:

public class Main {

    static class Animal {}

    static class Dog extends Animal {}

    static List<? extends Animal> foo() {
        List<Dog> dogs = new ArrayList<>();
        return dogs;
    }

    public static void main(String[] args) {
        List<Animal> dogs = Main.foo(); // compile error
    }
}

Я пытаюсь понять, почему он не компилируется. Это означает, почему компилятор не позволяет мне ссылаться на List<? extends Animal> как на List<Animal>? Это как-то связано с механизмом стирания шрифта?

Ответы [ 3 ]

10 голосов
/ 04 августа 2020

A List<Animal> - это List, к которому вы можете добавить любое Animal (или null), и все, что вы извлечете из него, будет Animal.

A List<? extends Animal> - это список, который содержит только определенный c подкласс из Animal (или null), и вы не знаете, какой из них; это позволяет вам рассматривать все, что вы извлекаете из него, как Animal, но вам не разрешается ничего добавлять к нему (кроме буквального null).

A List<? extends Animal> не может действовать как List<Animal>, потому что это позволит вам сделать это:

List<Cat> listOfCats = new ArrayList<>();
List<? extends Animal> listOfSomeAnimals = listOfCats;  // Fine.
List<Animal> listOfAnimals = listOfSomeAnimals;  // Error, pretend it works.
listOfAnimals.add(new Dog());

Теперь, потому что listOfCats, listOfSomeAnimals и listOfAnimals являются все тот же список, Dog добавлен к listOfCats. Как таковое:

Cat cat = listOfCats.get(0);  // ClassCastException.
4 голосов
/ 04 августа 2020

Потому что List<? extends Animal> допускает любой подкласс Animal. Список просто разрешает объекты класса Animal.

В List<? extends Animal> также разрешены объекты, такие как кошка или собака. Если вы начнете это с «чистого» списка собак, вы не сможете сказать извне, что это не разрешено, и поэтому он не компилируется.

2 голосов
/ 04 августа 2020

Со-, противоположность и инвариантность в 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. Но вы не можете поместить туда произвольных Животных. Вот почему это не удается.

...