Яка різниця між ? та Об'єкт у дженериках Java?


137

Я використовую Eclipse, щоб допомогти мені очистити код, щоб правильно користуватися Java-дженериками. Більшу частину часу він виконує відмінну роботу з виводних типів, але є деякі випадки, коли висновок типу повинен бути максимально загальним: Об'єкт. Але Eclipse, здається, дає мені можливість вибирати між типом Об'єкта та типом "?".

Тож яка різниця між:

HashMap<String, ?> hash1;

і

HashMap<String, Object> hash2;

4
Дивіться офіційний підручник з Wildcards . Це добре пояснює і наводить приклад того, чому це потрібно над просто використанням Object.
Ben S

Відповіді:


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 <?> на рівні контролера для всіх моїх функцій контролера?
Іраклі

бездоганна відповідь Йоганне!
gaurav

36

Ще один спосіб задуматися над цією проблемою - це

HashMap<String, ?> hash1;

еквівалентно

HashMap<String, ? extends Object> hash1;

З'єднайте ці знання з принципом "Отримати і покладіть" в розділі (2.4) з Java Generics and Collections :

Принцип "Get and Put": використовуйте розширені підмітні знаки, коли ви отримуєте лише значення зі структури, використовуйте супер підстановку, коли ви лише вводите значення в структуру, і не використовуйте підстановку, коли ви отримуєте і ставите.

і, сподіваємось, підводні карти можуть почати мати більше сенсу.


1
Якщо "?" плутає вас, "? Extends Object", швидше за все, вас бентежить. Може бути.
Майкл Майерс

Намагаючись надати „інструменти мислення”, щоб можна було міркувати про цю непросту тему. Надано додаткову інформацію про розширення макетів.
Жульєн Частанг

2
Дякуємо за додаткову інформацію. Я все ще перетравлюю це. :)
skiphoppy

HashMap<String, ? extends Object> тож це лише заважає nullдодаватись у хешмап?
mallaudin

12

Це легко зрозуміти, якщо ви пам’ятаєте, що Collection<Object>це просто родова колекція, яка містить об’єкти типу Object, але Collection<?>це супер тип усіх колекцій.


1
Слід зазначити, що це не зовсім просто ;-), але це правильно.
Шон Рейлі

6

Відповіді вище коваріації охоплюють більшість випадків, але пропускають одне:

"?" включно до "Об'єкта" в ієрархії класів. Можна сказати, що String - це тип об'єкта, а 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.


Msgstr "Ви нічого не можете безпечно помістити в Map <String,?>" False. МОЖЕТЕ, це і є його метою.
Ben S

3
Бен помиляється, єдине значення, яке ви можете помістити в колекцію типу <?>, Є нульовим, тоді як ви можете помістити що-небудь у колекцію типу <Object>.
ск.

2
За посиланням, яке я дав у своїй відповіді: "Оскільки ми не знаємо, що означає тип елемента c, ми не можемо до нього додавати об'єкти". Прошу вибачення за дезінформацію.
Ben S

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 може зберігати будь-яку з цих хеш-карт. Цю змінну не хвилює, який тип значення має хешмап, який він містить.

Однак, маючи підстановку, це не дозволяє вводити будь-який тип об’єкта на вашу карту. насправді, маючи хеш-карту вище, ви нічого не можете помістити в неї за допомогою mapзмінної:

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

Усі вищезазначені виклики методу призведуть до помилки часу компіляції, оскільки Java не знає, що таке значення типу HashMap всередині map.

Ви все ще можете отримати значення з хеш-карти. Хоча ви "не знаєте типу значення" (оскільки ви не знаєте, який тип хеш-карти знаходиться всередині вашої змінної), ви можете сказати, що все є підкласом 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));
}

У вищевказаному методі нам потрібно було б знати, що ключ Map - це Character, інакше ми не знаємо, який тип використовувати для отримання значень з нього. toString()Однак всі об'єкти мають метод, тому карта може мати будь-який тип об'єкта за його значеннями. Ми ще можемо надрукувати значення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.