В чем разница между? и Object в Java-дженериках?

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

Итак, какая разница между:

HashMap<String, ?> hash1;

и

HashMap<String, Object> hash2;

Ответ 1

Экземпляр 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) {
    ...
}

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

Ответ 2

Другой способ подумать об этой проблеме состоит в том, что

HashMap<String, ?> hash1;

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

HashMap<String, ? extends Object> hash1;

Объедините это знание с принципом "Get and Put" в разделе (2.4) из Java Generics and Collections:

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

"?" включает в себя "Объект" в иерархии классов. Можно сказать, что 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