Яка різниця між ? та Об'єкт у дженериках 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.