Не понимаю по какому принципу создается лист параметризованный WildCard

Рейтинг: 1Ответов: 1Опубликовано: 19.07.2023

Необходимо создать лист, принимающий на вход только объекты, наследующиеся от другого класса. В данном примере лист Vehicles, который должен принимать на вход объекты класса Car и Bicycle.

public class Test {
    public static void main(String[] args) {
        List<? extends Vehicle> vehicles = new ArrayList<>();
        vehicles.add(new Car()); // Ошибка компиляции
        vehicles.add(new Bicycle());
    }
}

class Vehicle{}
class Car extends Vehicle{}
class Bicycle extends Vehicle{}

При этом возникает ошибка компиляции, вариант же с <? super Vehicle> работает как требуется:

public static void main(String[] args) {
        List<? super Vehicle> vehicles = new ArrayList<>();
        vehicles.add(new Car());
        vehicles.add(new Bicycle());
    }

Разве в данном коде лист не должен принимать классы стоящие выше по иерархии наследования? То есть, например, чтобы запись <? super Car> принимала бы объект класса vehicle, который стоит выше.

Ответы

▲ 0Принят

Когда переменная-список объявлена с использованием вайлд-карда с верхней границей ? extends Vehicle, это значит, что список будет возвращать объекты типа Vehicle, т.е. самого класса или его наследников. Значит, работать будут методы чтения данных из такого списка List::get (Provider Extends в принципе PECS). При помощи метода add в такой список можно будет добавить только null значения.

Однако, инициализировать такой список всё же можно несколькими способами:

  • через конструктор, в который будет передана коллекция:
List<? extends Vehicle> vehicles = new ArrayList<>(Arrays.asList(new Car(), new Bicycle()));
Vehicle v = vehicles.get(0);
Bicycle b = (Bicycle) vehicles.get(1);
  • при помощи статического метода List.of (Java 9+):
List<? extends Vehicle> vehicles = List.of(new Car(), new Bicycle());
  • при помощи инициализации с двойными скобками, где также будет создан промежуточный анонимный класс для списка:
List<? extends Vehicle> vehicles = new ArrayList<>() {{
    add(new Car());  
    add(new Bicycle());
}};

Соответственно, если список определён как вайлдкард с нижней границей <? super Vehicle>, он будет приёмником (Consumer Super), то есть методы записи будут работать, а чтения -- уже нет.

List<? super Vehicle> vehicles = new ArrayList<>();
vehicles.add(new Car()); // ok
vehicles.add(new Bicycle());

//Vehicle v = vehicles.get(0); // ошибка компиляции
Bicycle b = (Bicycle) vehicles.get(1); // а здесь всё хорошо :)

Похожий вопрос (апрель 2019): Принцип generic типов Java