Jaka jest różnica pomiędzy ? i obiekt w Javie generics?

137

Używam Eclipse, aby pomóc mi oczyścić kod, aby poprawnie używać generycznych języków Java. W większości przypadków doskonale radzi sobie z wnioskami o typach, ale są pewne przypadki, w których wywnioskowany typ musi być tak ogólny, jak to tylko możliwe: Object. Ale wydaje się, że Eclipse daje mi możliwość wyboru między typem obiektu a typem „?”.

Jaka jest więc różnica między:

HashMap<String, ?> hash1;

i

HashMap<String, Object> hash2;
skiphoppy
źródło
4
Zobacz oficjalny samouczek dotyczący symboli wieloznacznych . Wyjaśnia to dobrze i daje przykład, dlaczego jest to konieczne, po prostu używając Object.
Ben S,

Odpowiedzi:

148

Wystąpienie HashMap<String, String>dopasowań, Map<String, ?>ale nie Map<String, Object>. Powiedzmy, że chcesz napisać metodę, która akceptuje mapy od Strings do wszystkiego: jeśli chcesz napisać

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

nie możesz dostarczyć HashMap<String, String>. Jeśli piszesz

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

to działa!

Czasami błędnie rozumiane jest to, że List<String>nie jest to podtyp List<Object>. (Ale String[]w rzeczywistości jest to podtyp Object[], jest to jeden z powodów, dla których typy generyczne i tablice nie mieszają się dobrze (tablice w Javie są kowariantne, typy generyczne nie są, są niezmienne )).

Przykład: Jeśli chcesz napisać metodę, która akceptuje Lists z InputStreamsi podtypy InputStream, napisz

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

Przy okazji: Efektywna Java Joshua Blocha to doskonałe źródło informacji, jeśli chcesz zrozumieć nie takie proste rzeczy w Javie. (Twoje pytanie powyżej jest również bardzo dobrze opisane w książce).

Johannes Weiss
źródło
1
czy jest to właściwy sposób używania ResponseEntity <?> na poziomie kontrolera dla wszystkich moich funkcji kontrolera?
Irakli
bezbłędna odpowiedź Johannes!
gaurav
36

Innym sposobem myślenia o tym problemie jest to

HashMap<String, ?> hash1;

jest równa

HashMap<String, ? extends Object> hash1;

Połącz tę wiedzę z zasadą „Get and Put Principle” w sekcji (2.4) w dokumencie Java Generics and Collections :

Zasada pobierania i wysyłania: używaj symbolu wieloznacznego extends, gdy otrzymujesz wartości tylko ze struktury, używaj super-wieloznacznika, gdy umieszczasz tylko wartości w strukturze, i nie używaj symbolu wieloznacznego, gdy zarówno otrzymujesz, jak i wstawiasz.

i miejmy nadzieję, że dzika karta może zacząć mieć więcej sensu.

Julien Chastang
źródło
1
Jeśli "?" dezorientuje Cię, „? extends Object” prawdopodobnie jeszcze bardziej zmyli Cię. Może.
Michael Myers
Próba dostarczenia „narzędzi do myślenia”, które pozwolą wnioskować na ten trudny temat. Podano dodatkowe informacje o rozszerzaniu symboli wieloznacznych.
Julien Chastang,
2
Dzięki za dodatkowe info. Nadal to trawię. :)
skiphoppy
HashMap<String, ? extends Object> więc zapobiega tylko nulldodaniu do hasmapy?
mallaudin
12

Łatwo to zrozumieć, jeśli pamiętasz, że Collection<Object>jest to tylko ogólna kolekcja, która zawiera obiekty typu Object, ale Collection<?>jest to nadtyp wszystkich typów kolekcji.

topchef
źródło
1
Należy zauważyć, że nie jest to do końca łatwe ;-), ale tak jest.
Sean Reilly,
6

Odpowiedzi powyżej kowariancji obejmują większość przypadków, ale pomijają jedną rzecz:

„?” zawiera „Obiekt” w hierarchii klas. Można powiedzieć, że String jest typem Object, a Object jest typem?. Nie wszystko pasuje do Object, ale wszystko pasuje?

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
Eyal
źródło
4

Nie możesz bezpiecznie niczego włożyć Map<String, ?>, ponieważ nie wiesz, jakiego typu mają być wartości.

Możesz umieścić dowolny obiekt w a Map<String, Object>, ponieważ wiadomo, że wartością jest Object.

erickson
źródło
„Nie możesz bezpiecznie umieścić niczego w Map <String,?>” Fałsz. MOŻESZ, to jest jego cel.
Ben S,
3
Ben się myli, jedyną wartością, jaką możesz umieścić w kolekcji typu <?>, Jest null, podczas gdy możesz umieścić wszystko w kolekcji typu <Object>.
sk.
2
Z linku, który podałem w mojej odpowiedzi: „Ponieważ nie wiemy, co oznacza element typu c, nie możemy dodawać do niego obiektów.”. Przepraszam za dezinformację.
Ben S,
1
największym problemem jest to, że nie rozumiem, jak to możliwe, że nie możesz nic dodać do HashMap <String,?>, jeśli weźmiemy pod uwagę, że WSZYSTKO oprócz prymitywów jest obiektami. Czy jest to dla prymitywów?
avalon
1
@avalon W innym miejscu znajduje się odniesienie do tej mapy, która jest ograniczona. Na przykład może to być plik Map<String,Integer>. Tylko Integerobiekty powinny umieszczać zapisane na mapie jako wartości. Ale ponieważ nie wiem typ wartości (to ?), nie wiem, czy jeśli to jest bezpieczne, aby zadzwonić put(key, "x"), put(key, 0)lub cokolwiek innego.
erickson
2

Zadeklarowanie hash1jako a HashMap<String, ?>dyktuje, że zmienna hash1może przechowywać dowolną, HashMapktóra ma klucz Stringi dowolny typ wartości.

HashMap<String, ?> map;
map = new HashMap<String, Integer>();
map = new HashMap<String, Object>();
map = new HashMap<String, String>();

Wszystkie powyższe są ważne, ponieważ zmienna map może przechowywać dowolną z tych map skrótów. Ta zmienna nie obchodzi, jaki jest typ wartości, jaki zawiera hashmap.

Posiadanie wieloznaczny nie nie , jednak pozwala umieścić każdy rodzaj obiektu na mapie. w rzeczywistości, z powyższą mapą hashującą, nie możesz nic do niej wstawić używając mapzmiennej:

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

Wszystkie powyższe wywołania metod spowodują błąd w czasie kompilacji, ponieważ Java nie wie, jaki jest typ wartości HashMap wewnątrz map.

Nadal możesz pobrać wartość z mapy skrótów. Chociaż „nie znasz typu wartości” (ponieważ nie wiesz, jaki typ mapy skrótów znajduje się w zmiennej), możesz powiedzieć, że wszystko jest podklasą, Objecta więc cokolwiek wyjdziesz z mapy będzie typu 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);

Powyższy blok kodu wypisze 10 na konsoli.


Aby zakończyć, użyj HashMapznaku wieloznacznego, gdy nie obchodzi Cię (tj. Nie ma znaczenia), jakie są typy HashMap, na przykład:

public static void printHashMapSize(Map<?, ?> anyMap) {
    // This code doesn't care what type of HashMap is inside anyMap.
    System.out.println(anyMap.size());
}

W przeciwnym razie określ potrzebne typy:

public void printAThroughZ(Map<Character, ?> anyCharacterMap) {
    for (int i = 'A'; i <= 'Z'; i++)
        System.out.println(anyCharacterMap.get((char) i));
}

W powyższej metodzie musielibyśmy wiedzieć, że klucz mapy to a Character, w przeciwnym razie nie wiedzielibyśmy, jakiego typu użyć, aby uzyskać z niego wartości. Wszystkie obiekty mają jednak toString()metodę, więc mapa może mieć dowolny typ obiektu dla swoich wartości. Nadal możemy wydrukować wartości.

Kröw
źródło