В чем разница между ? и объект в дженериках Java?

137

Я использую Eclipse, чтобы помочь мне очистить некоторый код для правильного использования обобщений Java. Большую часть времени он отлично справляется с выводом типов, но в некоторых случаях выводимый тип должен быть как можно более универсальным: Object. Но Eclipse, похоже, дает мне возможность выбирать между типом Object и типом «?».

Так в чем же разница между:

HashMap<String, ?> hash1;

и

HashMap<String, Object> hash2;
skiphoppy
источник
4
Смотрите официальный учебник по Wildcards . Это объясняет это хорошо и дает пример того, почему это необходимо по сравнению с простым использованием Object.
Бен С

Ответы:

148

Экземпляр HashMap<String, String>спичек, Map<String, ?>но нет Map<String, Object>. Скажем, вы хотите написать метод, который принимает карты от Strings к чему-либо: если бы вы написали

public void foobar(Map<String, Object> ms) {
    ...
}

Вы не можете поставить HashMap<String, String>. Если ты пишешь

public void foobar(Map<String, ?> ms) {
    ...
}

оно работает!

В дженериках Java иногда неправильно понимают, что List<String>это не подтип List<Object>. (Но String[]на самом деле это подтип Object[], это одна из причин, по которой дженерики и массивы плохо сочетаются (массивы в Java ковариантны, дженерики - нет, они инвариантны )).

Пример: если вы хотите написать метод, который принимает Lists из InputStreams и подтипы InputStream, вы должны написать

public void foobar(List<? extends InputStream> ms) {
    ...
}

Кстати, «Эффективная Java» Джошуа Блоха - отличный ресурс, когда вы хотите понять не такие простые вещи в Java. (Ваш вопрос выше также очень хорошо освещен в книге.)

Йоханнес Вайс
источник
1
это правильный способ использовать ResponseEntity <?> на уровне контроллера для всех моих функций контроллера?
Ираклий
безупречный ответ Йоханнес!
Гаурав
36

Еще один способ думать об этой проблеме заключается в том, что

HashMap<String, ?> hash1;

эквивалентно

HashMap<String, ? extends Object> hash1;

Соедините эти знания с «Принципами получения и сдачи» в разделе (2.4) из Общих принципов и коллекций Java :

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

и можно надеяться, что дикая карта может начать иметь больше смысла.

Жюльен Частанг
источник
1
Если "?" смущает вас, «расширяет объект», скорее всего, смущает вас больше. Может быть.
Майкл Майерс
Попытка предоставить «инструменты мышления», позволяющие рассуждать об этом сложном предмете. Предоставлена ​​дополнительная информация о расширении подстановочных знаков.
Жюльен Частанг
2
Спасибо за дополнительную информацию. Я все еще перевариваю это. :)
skiphoppy
HashMap<String, ? extends Object> так это только мешает nullбыть добавленным в hashmap?
Маллаудин
12

Это легко понять, если вы помните, что Collection<Object>это просто универсальная коллекция, которая содержит объекты типа Object, но Collection<?>является супертипом всех типов коллекций.

topchef
источник
1
Нужно сделать вывод, что это не совсем просто ;-), но это правильно.
Шон Рейли
6

Ответы выше ковариации покрывают большинство случаев, но пропускают одно:

"?" включает в себя «Объект» в иерархии классов. Можно сказать, что String - это тип Object, а Object - это тип? Не все соответствует объекту, но все соответствует?

int test1(List<?> l) {
  return l.size();
}

int test2(List<Object> l) {
  return l.size();
}

List<?> l1 = Lists.newArrayList();
List<Object> l2 = Lists.newArrayList();
test1(l1);  // compiles because any list will work
test1(l2);  // compiles because any list will work
test2(l1);  // fails because a ? might not be an Object
test2(l2);  // compiled because Object matches Object
Эяль
источник
4

Вы не можете безопасно вложить что-либо Map<String, ?>, потому что вы не знаете, какого типа должны быть значения.

Вы можете поместить любой объект в Map<String, Object>, потому что известно, что это значение Object.

Эриксон
источник
"Вы не можете безопасно положить что-нибудь в Map <String,?>" Ложь. Вы МОЖЕТЕ, это его цель.
Бен С
3
Бен не прав, единственное значение, которое вы можете поместить в коллекцию типа <?>, Равно null, тогда как вы можете поместить что-либо в коллекцию типа <Object>.
ск.
2
По ссылке, которую я дал в своем ответе: «Поскольку мы не знаем, что обозначает тип элемента c, мы не можем добавлять к нему объекты». Я прошу прощения за дезинформацию.
Бен С
1
самая большая проблема в том, что я не понимаю, как это возможно, что вы ничего не можете добавить в HashMap <String,?>, если учесть, что ВСЕ, кроме примитивов, является объектами. Или это для примитивов?
Авалон
1
@avalon В другом месте есть ссылка на эту карту, которая ограничена. Например, это может быть Map<String,Integer>. Только Integerобъекты должны быть сохранены на карте в качестве значений. Но так как вы не знаете тип значения (это ?), вы не знаете , если , если это безопасно звонить put(key, "x"), put(key, 0)или что - нибудь еще.
Эриксон
2

Объявление hash1как HashMap<String, ?>диктует, что переменная hash1может содержать любой, HashMapкоторый имеет ключ Stringи любой тип значения.

HashMap<String, ?> map;
map = new HashMap<String, Integer>();
map = new HashMap<String, Object>();
map = new HashMap<String, String>();

Все вышеперечисленное является действительным, потому что переменная map может хранить любую из этих хэш-карт. Эта переменная не заботится о том, что представляет собой тип Value, из хеш-карты, которую он содержит.

Имея подстановочные вовсе не , однако, позволяют поместить любой тип объекта в вашу карту. на самом деле, с хэш-картой выше, вы не можете поместить в нее что-либо, используя mapпеременную:

map.put("A", new Integer(0));
map.put("B", new Object());
map.put("C", "Some String");

Все приведенные выше вызовы методов приведут к ошибке во время компиляции, поскольку Java не знает, что такое тип Value внутри HashMap map.

Вы все еще можете получить значение из хэш-карты. Хотя вы «не знаете тип значения» (потому что вы не знаете, какой тип хеш-карты находится внутри вашей переменной), вы можете сказать, что все является подклассом Objectи, таким образом, все, что вы получаете из карты. будет иметь тип Object:

HashMap<String, Integer> myMap = new HashMap<>();// This variable is used to put things into the map.

myMap.put("ABC", 10);

HashMap<String, ?> map = myMap;
Object output = map.get("ABC");// Valid code; Object is the superclass of everything, (including whatever is stored our hash map).

System.out.println(output);

Приведенный выше блок кода выведет 10 на консоль.


Итак, чтобы закончить, используйте HashMapс подстановочными знаками, когда вам все равно (то есть, это не имеет значения), какие типы HashMap, например:

public static void printHashMapSize(Map<?, ?> anyMap) {
    // This code doesn't care what type of HashMap is inside anyMap.
    System.out.println(anyMap.size());
}

В противном случае укажите типы, которые вам нужны:

public void printAThroughZ(Map<Character, ?> anyCharacterMap) {
    for (int i = 'A'; i <= 'Z'; i++)
        System.out.println(anyCharacterMap.get((char) i));
}

В приведенном выше методе нам нужно знать, что ключ карты является a Character, иначе мы не знали бы, какой тип использовать для получения значений из него. Однако все объекты имеют toString()метод, поэтому карта может иметь любой тип объекта для своих значений. Мы все еще можем напечатать значения.

Krow
источник