В чем разница между? и объект в дженериках Java?
Я использую Eclipse, чтобы помочь мне очистить некоторый код для правильного использования обобщений Java. Большую часть времени он отлично справляется с выводом типов, но в некоторых случаях выводимый тип должен быть как можно более универсальным: Object. Но Eclipse, похоже, дает мне возможность выбирать между типом Object и типом "?".
Так в чем же разница между:
HashMap<String, ?> hash1;
а также
HashMap<String, Object> hash2;
6 ответов
Экземпляр HashMap<String, String>
Матчи Map<String, ?>
но нет Map<String, Object>
, Скажем, вы хотите написать метод, который принимает карты от String
с чем угодно: если бы вы написали
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. (Ваш вопрос выше также очень хорошо освещен в книге.)
Еще один способ думать об этой проблеме заключается в том, что
HashMap<String, ?> hash1;
эквивалентно
HashMap<String, ? extends Object> hash1;
Соедините эти знания с "Принципами получения и сдачи" в разделе (2.4) из Java Generics and Collections:
Принцип Get и Put: используйте подстановочный знак расширения, когда вы только получаете значения из структуры, используйте супер подстановочный знак, когда вы только помещаете значения в структуру, и не используйте подстановочный знак, когда вы получаете и кладете.
и можно надеяться, что дикая карта может начать иметь больше смысла.
Это легко понять, если вспомнить Collection<Object>
это просто общая коллекция, которая содержит объекты типа Object
, но Collection<?>
это супер тип всех типов коллекций.
Ответы выше ковариации покрывают большинство случаев, но пропускают одно:
"?" Включает "Объект" в иерархии классов. Можно сказать, что 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
Вы не можете безопасно положить что-нибудь в Map<String, ?>
потому что вы не знаете, какого типа должны быть значения.
Вы можете поместить любой объект в Map<String, Object>
потому что значение, как известно, является Object
,
Декларирование 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));
}
В приведенном выше методе нам нужно знать, что ключ карты Character
иначе мы бы не знали, какой тип использовать для получения значений из него. Все объекты имеют toString()
метод, однако, поэтому карта может иметь любой тип объекта для своих значений. Мы все еще можем напечатать значения.