почему List <? super Number> может содержать строку - PullRequest
0 голосов
/ 31 октября 2018

Я пытаюсь понять подстановочные знаки в дженериках, и у меня есть вопрос Список <? super Number > может ссылаться на любой список объектов и добавлять любые объекты, расширяющие число, в этот список, но я не могу добавить в него объект, не расширяющий число (строка). но почему я могу сделать это в этом коде без каких-либо ошибок или исключений компиляции во время выполнения (ссылаясь на список, содержащий объект String)

edit: что я хочу понять, что дженерики обеспечивают безопасность времени компиляции, а в моем примере это не достигается

List <? super Object> objectList = new ArrayList<>();
objectList.add("str1");

List<? super Number> numberList = objectList;
numberList.add(1);

objectList.add("str2");
for (int i = 0; i < objectList.size(); i++) {
    System.out.println(objectList.get(i) + "");
} 

Ответы [ 4 ]

0 голосов
/ 01 ноября 2018

Когда компилятор видит:

List<? super Number> numberList = objectList;

Сначала фиксирует подстановочный знак. Общий тип становится Y = X > Number (имеется в виду конкретный супертип Number). Итак имеем:

List<Y> numberList = objectList //with type of List<Object>;

Затем компилятор определяет, что Y можно заменить на Object . Следовательно, типы идентичны, и numberList разрешено указывать на тот же объект, что и objectList.

Затем сгенерированный байт-код передается в исполняющую систему для выполнения. Что касается системы времени выполнения, оба списка имеют тип java.util.ArrayList из-за типа стирания . Поэтому исключение времени выполнения не будет вызываться при помещении строк или других объектов в этот контейнер.

Но я также чувствую, что здесь что-то не так. Чтобы перефразировать ваш вопрос:

Что может сделать компилятор для предотвращения этой ситуации?

Обратите внимание, что компилятор НЕ ДОЛЖЕН пожаловаться во время присваивания , потому что :

Принцип безопасного создания экземпляров: Создание параметрического класса с типами, которые соответствуют заявленным ограничениям на параметры, должно: не вызывает ошибку.

Я думаю, что этот принцип применим и к заданиям. Назначение не нарушает никаких правил языка, поэтому компилятор не должен выдавать ошибку.

Таким образом, единственное место, где можно спасти программиста от аварии, - это операция add. Но что может делать компилятор? Если он запрещает операцию add на objectList из-за назначения, это нарушит правила других языков. Если он увеличивает add для поддержки добавления объектов в numberList, это также нарушит некоторые другие языковые правила.

Я не могу придумать ни одного простого и легкого решения, которое не сломало бы много вещей для исправления чего-то, что могло бы даже не быть проблемой, и программист определенно находится в хорошем положении, чтобы принять решение.

Проверка типов предназначена для того, чтобы помочь программисту не заменить ее. Еще один пример его несовершенства:

public static void main(String[] args) {
    Object m = args;
    String[] m2 = m; //complains, despite m2 definitely being an String[]
}

P.S .: Я нашел приведенный выше пример на SO, но, к сожалению, потерял ссылку!

0 голосов
/ 31 октября 2018

У вас есть два различных типа полиморфизма, которые взаимодействуют здесь, запутанно.

Ключом к пониманию этого является то, что в дополнение к параметрическому полиморфизму (то есть, дженерики) у вас также есть полиморфизм подтипа , то есть классический объектно-ориентированный отношения "is-a".

В Java все объекты являются подтипами Object. Таким образом, контейнер, который может содержать Object значений, может содержать любое значение.

Если мы переписываем все общие границы как просто <Object>, тогда код работает так же, и, очевидно, так:

List<Object> objectList = new ArrayList<>();
objectList.add("str1");

List<Object> numberList = objectList;
numberList.add(1);

objectList.add("str2");
for (int i = 0; i < objectList.size(); i++) {
    System.out.println(objectList.get(i) + "");
}

В частности, objectList.get(i) + "" оценивается как нечто, вызывающее objectList.get(i).toString(), а поскольку toString() является методом Object, он будет работать независимо от типа объектов в objectList.

Что не сработает, так это:

Number number = numberList.get(i);  // error!

Это потому, что, несмотря на вводящее в заблуждение имя, numberList не обязательно содержит только Number объектов и может фактически не содержать никаких Number объектов вообще!

Давайте пройдемся и посмотрим, почему это так.

Сначала мы создадим список объектов:

List<? super Object> objectList = new ArrayList<>();

Что означает этот тип? Тип List<? super Object> означает «список объектов какого-либо типа, я не могу сказать вам, какой тип, но я знаю, что любой тип это либо Object, либо супертип Object». Мы уже знаем, что Object является корнем иерархии подтипов, так что это фактически то же самое, что и List<Object>: то есть этот объект может содержать только Object объектов.

Но ... это не совсем верно. Список может содержать только Object объектов, но объект Object может быть любым! Фактические объекты в типе runtype могут быть любого типа, который является подтипом Object (так, что угодно, кроме примитива), но, помещая их в этот список, вы теряете возможность сказать, какие объекты они представляют больше - они могут быть чем угодно. Это нормально для того, что делает остальная часть этой программы, потому что все, что ей нужно сделать, это вызвать toString() для объектов, и это можно сделать, потому что они все расширяют Object.

Теперь давайте посмотрим на объявление другой переменной:

List<? super Number> numberList = objectList;

Опять же, что означает тип List<? super Number>? Важно то, что это означает «список объектов какого-то типа, я не могу сказать вам, какой это тип, но я знаю, что какой бы это ни был тип, это либо Number, либо какой-то супертип Number». Ну, с левой стороны у нас есть список "Number или некоторый супертип Number", а с правой стороны у нас есть список Object - ясно, что Object является супертипом Number и так что этот список является списком Object. Все проверки типов (и, вопреки моему первоначальному комментарию, без каких-либо предупреждений).

Таким образом, возникает вопрос: почему List<? super Number> может содержать String? Потому что List<? super Number> может быть просто List<Object>, а List<Object> может содержать String, потому что String is-a Object.

0 голосов
/ 31 октября 2018

Ссылка типа List<? super Number> может относиться либо к List<Object>, либо к List<Number>. Любая операция по этой ссылке должна работать с любым из этих типов. Вы не можете добавить строку с помощью ссылки List<? super Number>, поскольку операция будет работать только с одним из возможных типов объектов, но вы можете с помощью ссылки List<? super Object>.

A List<? super Object> может относиться только к List<Object>. List<? super Number> может относиться либо к List<Number>, либо к List<Object>. Это более общий тип, поэтому присваивание разрешено.

0 голосов
/ 31 октября 2018

Это нижний ограниченный шаблон в шаблонах Java.

Согласно документации Java, подстановочный знак с ограничением снизу ограничивает неизвестный тип конкретным типом или супертипом этого типа.

Изначально вы создаете список с типом объекта или супер типом объекта. Как вы знаете, в Java каждый класс имеет объект в качестве суперкласса. Таким образом, мы можем использовать класс String как экземпляр Object. Поскольку в вашем списке разрешен тип Object, он также может разрешать тип String.

То же самое относится и к List<? super Number> numberList = objectList;, но вы не можете сделать наоборот.

Обратитесь, чтобы получить больше понимания о подстановочных знаках нижней границы: Подстановочные знаки нижней границы Java https://docs.oracle.com/javase/tutorial/java/generics/lowerBounded.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...