Сопоставьте ковариантность массивов Java с инвариантностью обобщённых коллекций: какой механизм безопасности обеспечивает каждое решение?
Массивы в Java ковариантны: массив подтипа можно рассматривать как массив супертипа. Это поддерживает проверку совместимости во время выполнения, поэтому ошибочная запись может привести к ArrayStoreException.
Обобщённые типы инвариантны: List<Подтип> не является List<Супертип>. Такая модель выявляет потенциально небезопасную запись уже на этапе компиляции, а wildcard позволяет явно выразить допустимую вариативность.
Массивы появились в Java раньше обобщений и изначально проектировались с ковариантностью, чтобы сохранять совместимость с объектно-ориентированным полиморфизмом. Например, код, работающий с массивом объектов, мог принимать массив конкретного класса.
Обобщения были добавлены позднее для статической типобезопасности и реализованы со стиранием типов. Поэтому для них выбрали инвариантность: она не допускает опасные присваивания, которые невозможно надёжно проверить по параметру типа во время выполнения.
Пусть Кошка является подтипом Животное. Если разрешить считать список кошек списком животных, в такой список можно было бы записать собаку. Чтение из списка кошек после этого нарушило бы его фактический контракт.
У массивов подобная запись компилируется, но JVM знает реальный компонентный тип массива и проверяет каждую запись. У обобщённых коллекций параметр типа после компиляции обычно недоступен, поэтому аналогичную защиту на уровне JVM обеспечить нельзя.
Ковариантность массива позволяет присваивание массива подтипа переменной массива супертипа. Однако фактический объект всё равно сохраняет компонентный тип, и при записи неподходящего значения JVM выполняет проверку.
В обобщённых типах такое присваивание запрещено, потому что List<Cat> не является List<Animal>. Иначе метод, принимающий List<Animal>, получил бы возможность добавить туда Dog.
Для безопасного чтения используется верхняя граница wildcard: List<? extends Animal>. Для безопасной записи объектов типа Cat в коллекцию кошек или её супертипа используется нижняя граница: List<? super Cat>. Wildcard делает разрешённую вариативность явной, а не меняет инвариантность самого обобщённого типа.
Главный компромисс массивов — совместимость и удобная ковариантность ценой ошибок во время выполнения. Главный компромисс обобщений — более строгая запись типов и необходимость использовать wildcard или параметр типа, когда требуется выразить отношение между типами.
Сервис принимает последовательность животных для обработки. Вариант с массивом позволяет передать массив кошек как массив животных, но ошибка обнаружится только при попытке записать в него объект другого вида. Это опасно для библиотечного API: место возникновения ошибки может находиться далеко от места неправильного присваивания.
Вариант с List<Animal> безопаснее, но напрямую не принимает List<Cat>, поскольку коллекции инвариантны. Вариант с List<? extends Animal> подходит, если сервис только читает животных; он запрещает добавление произвольных животных и тем самым сохраняет безопасность.
Для API чтения выбирают верхнюю границу wildcard, а для API записи — нижнюю. В результате ошибка выявляется компилятором, контракт метода отражает реальные операции, а вызывающий код не зависит от исключения времени выполнения, аналогичного ArrayStoreException.
List<Cat> и List<Animal> вообще нельзя выразить отношение?Нет. Само отношение параметризаций не возникает автоматически, но его можно выразить через wildcard: List<? extends Animal> описывает коллекцию некоторого неизвестного подтипа Animal. Такой параметр безопасен для чтения как Animal, но не для добавления конкретного животного.
Объект массива хранит компонентный тип, например фактический тип массива кошек. JVM использует эту информацию при каждой записи. Параметризация обычной коллекции стирается, а сама коллекция хранит элементы через обычные ссылки; проверка соответствия Cat или Animal не является универсальным механизмом JVM для её параметра типа.
Wildcard предоставляет ограниченное представление о допустимой вариативности, но не делает List<Cat> подтипом List<Animal>. List<? extends Animal> — отдельный тип, описывающий коллекцию с неизвестным конкретным параметром. Это безопасный интерфейс доступа к инвариантной коллекции, а не изменение правил наследования параметризованных типов.