В чем разница между ? и объект в дженериках Java? - PullRequest
126 голосов
/ 24 марта 2009

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

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

HashMap<String, ?> hash1;

и

HashMap<String, Object> hash2;

Ответы [ 6 ]

131 голосов
/ 24 марта 2009

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

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

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

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

это работает!

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

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

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

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

32 голосов
/ 25 марта 2009

Еще один способ думать об этой проблеме - это

HashMap<String, ?> hash1;

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

HashMap<String, ? extends Object> hash1;

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

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

и, возможно, джокер станет более понятным.

11 голосов
/ 24 марта 2009

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

4 голосов
/ 26 ноября 2014

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

"?" Включает «Объект» в иерархии классов. Можно сказать, что 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 голосов
/ 24 марта 2009

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

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

2 голосов
/ 04 июля 2018

Объявление 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 не знает, что такое тип значения 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));
}

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

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