Почему 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>

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

  • ArrayList
  • List
  • 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 сам объект.