Почему HashMap с общим объявлением "<? Super ArrayList> не принимает значение" новый объект() "в методе put?
Во время работы над вопросами интервью я столкнулся с ниже кодом:
List<Object> list = new ArrayList();
Map<Object, ? super ArrayList> m = new HashMap<Object, ArrayList>();
m.put(1, new Object());
m.put(2, list);
Вышеуказанный метод put бросает ошибку времени компиляции. Но когда я добавляю m.put(3, new ArrayList());
, он добавляет к карте без ошибки времени компиляции.
Для меня очень ясно, что я могу добавить new Object()
как значение в HashMap
из-за того, что объявление карты имеет тип < ? super ArrayList>
; это означает, что я могу добавить любое значение, превышающее ArrayList
(т.е. super of ArrayList
) и ArrayList
объект, но ничего не ниже ArrayList
.
Эта конкретная концепция очень хорошо написана в SCJP 6 Кэти Сьеррой и Берт Бейтс, и на основе этой теории и примеров я предположил, что она должна выполнять работу, как я понял. Может кто-нибудь помочь мне понять ошибку?
Ответы
Ответ 1
Тип ? super ArrayList
означает неизвестный тип, который имеет нижнюю границу ArrayList
. Например, это может быть Object
, но может быть AbstractList
или ArrayList
.
Единственное, что может знать компилятор, это то, что тип значения, хотя и неизвестен, гарантированно не более определен, чем ArrayList
, поэтому любой объект, который является ArrayList
, или подкласс ArrayList
.
Также помните: компилятор переходит только объявленным типом; он игнорирует назначенный тип.
Почему put(1, new Object())
не работает:
Ясно, что Object
не находится в пределах.
Почему put(1, list)
не работает:
Переменная имеет тип List
, который может содержать ссылку на LinkedList
, которая не находится в пределах требуемых границ.
Ответ 2
Вы не понимаете, что означает подстановочный знак ?
. Вероятно, у вас распространенное заблуждение:
Это не означает, что вы можете поместить любой объект на карте, который является суперклассом ArrayList
.
Что это значит, что значения на карте имеют неизвестный тип, который является супертипом ArrayList
. Поскольку точный тип неизвестен, компилятор не позволит вам помещать значение любого другого типа, кроме ArrayList
, в качестве значения на карте, - компилятор не располагает достаточной информацией, чтобы проверить, действительно ли то, что вы делаете, типобезопасный.
Предположим, что это разрешено, тогда вы можете делать такие плохие вещи:
Map<Object, ArrayList> m1 = new HashMap<Object, ArrayList>();
Map<Object, ? super ArrayList> m2 = m1;
// This should not be allowed, because m2 is really a HashMap<Object, ArrayList>
m2.put(1, new Object());
Ответ 3
? super ArrayList
означает "Я не знаю точно, что это за тип, но я знаю, что в любом месте, где требуется, чтобы я предоставил экземпляр этого типа, я могу с уверенностью дать ему ArrayList
". Фактический тип, который создает экземпляр шаблона, может быть Object
, Collection
, List
или ArrayList
, поэтому вы можете видеть, что new Object()
не может быть гарантировано безопасным, но new ArrayList()
может.
Ответ 4
? super ArrayList
не означает "любое значение, превышающее ArrayList
". Это означает "любое значение неизвестного типа ?
, которое является супертипом ArrayList
". Вы можете видеть это в своем коде, фактически, поскольку значения m
имеют тип ArrayList
:
HashMap<Object, ArrayList> hm = new HashMap<Object, ArrayList>();
Map<Object, ? super ArrayList> m = hm;
m.put(1, new Object());
ArrayList a = hm.get(1);
Что-то явно не так с этим кодом, потому что a
должен быть ArrayList
, а не Object
.
Ответ 5
Map<Object, ? super ArrayList> m
может быть ссылкой для многих карт, например
-
HashMap<Object, ArrayList>
-
HashMap<Object, List>
-
HashMap<Object, Object>
но эти карты имеют свою собственную цель, которая заключается в том, чтобы удерживать именно эти типы
или их подтипы , но не их супертипы, потому что супертипы могут не иметь всех необходимых членов (методов/полей), которые имеют подтипы.
Рассмотрим этот пример
List<Banana> bananas = new ArrayList<>();//list only for bananas <------+
List<? super Banana> list = bananas;//this is fine |
// |
list.add(new Object);//error, and can thank compiler for that |
//because you just tried to add Object to this list --+
//which should contain only Bananas
list.add(new Banana());// this is FINE because whatever precise type of list
// it can accept also banana
Чтобы проиллюстрировать последний комментарий, используйте
List<Fruit> fruits = new ArrayList<>();//can contain Bananas, Apples, and so on
List<? super Banana> list = fruits;//again, this is fine
list.add(new Object)//wrong, as explained earlier
list.add(new Banana());// OK because we know that list will be of type which
// can also accept Banana
Ответ 6
Я считаю, что ни один из ответов не дает действительно удовлетворительного объяснения. Мне пришлось "бежать в библиотеку" и прочитать главу 5 "Эффективной Java" Джошуа Блоха, чтобы наконец понять, как это работает.
Этот вопрос несколько усложняется с помощью Map и raw ArrayList. Это не способствует ясности вопроса. В основном вопрос о том, что означают следующие общие декларации и как они отличаются:
Collection<? super E> x;
Collection<? extends E> y;
x = набор неизвестного типа, который является E или суперклассом E.
y = набор неизвестного типа, который является E или подклассом E.
Для этого может быть добавлена возможность использования для x: E или любого подкласса E.
Задание для y: E или любого подкласса E может быть извлечено из него.
Это также известно под аббревиатурой PECS: Producer-extends, Consumer-supers.
Если сборник должен иметь возможность "потреблять" E
(E
может быть добавлен к нему), объявите его как ? super E
.
Если сборка должна иметь возможность "производить" E
, (E
можно извлечь из нее), объявите общий текст как ? extends E
.
Надеюсь, это пояснит, почему код в вопросе не компилируется: на карту может быть добавлен только ArrayList или подтип ArrayList.
Ответ 7
Вы можете ссылаться на спецификацию java, глава 4.5.1. Тип Аргументы и подстановочные знаки; он утверждает:
В отличие от обычных переменных типа, объявленных в сигнатуре метода, при использовании подстановочного символа не требуется вывод типа. Следовательно, допустимо объявлять нижние границы в подстановочном знаке, используя следующий синтаксис, где B - нижняя граница:
? супер B
Нижняя граница означает, что допустимые типы - все, что больше или равно, чем B (в вашем случае ArrayList
). Ни Object
, ни List
больше, чем ArrayList
, поэтому вы получаете ошибку компиляции.
Чтобы продолжить тестирование, попробуйте объявить новый настраиваемый тип, например public class MyArrayList extends ArrayList
, и использовать его в своем Map
: второй put
будет работать.
Или объявите карту как Map<Object, ? super List>
: снова будет работать второй put
.
Вы не можете поместить Object
в такую карту: Object
- это корень типа класса java, поэтому он "меньше", чем все.
EDIT: после голосования, я попытался понять, что я что-то не понял. Этот образец компилируется (со стандартом javac
):
import java.util.*;
public class test {
public static void main(String[] args) {
ArrayList list = new ArrayList();
Map<Object, ? super List> m = new HashMap<Object, List>();
m.put(2, list);
}
}
Таким образом, нижняя граница действительно означает, что вы можете поставить все больше или равно.
Ответ 8
при вводе
Map<Object, ? super ArrayList> m = new HashMap<Object,
ArrayList>();
ваш "?" стать ArrayList
поэтому вы не можете ничего добавить, кроме расширений ArrayList
Ответ 9
Существуют общие возможности для обеспечения такого поведения. Вы ограничиваете записи карт общим. Ваш верхний предел - ArrayList, но нижний предел открыт с любым подклассом ArrayList. Таким образом, вы не можете помещать записи Объекта в эту карту.
Ответ 10
То, что вы пытаетесь сказать, верно. Когда вы просто сушите код, имеет смысл, что все, что является SuperClass of ArrayList, можно поместить в качестве значения в Map.
Но проблема в том, что это по-дизайн. Общая проверка выполняется просто во время компиляции не во время выполнения. Поэтому во время компиляции, хотя вы знаете, что Object является суперклассом ArrayList; компилятор этого не знает. Поэтому компилятор будет жаловаться на это. Единственное, что может быть поставлено как значение, это null
или ArrayList
сам объект.