Was ist der Unterschied zwischen ? und Objekt in Java-Generika?

137

Ich verwende Eclipse, um Code zu bereinigen und Java-Generika ordnungsgemäß zu verwenden. Meistens leistet es hervorragende Arbeit, Typen abzuleiten, aber es gibt einige Fälle, in denen der abgeleitete Typ so allgemein wie möglich sein muss: Objekt. Aber Eclipse scheint mir die Möglichkeit zu geben, zwischen einem Objekttyp und einem '?' Typ zu wählen.

Was ist der Unterschied zwischen:

HashMap<String, ?> hash1;

und

HashMap<String, Object> hash2;
Skiphoppy
quelle
4
Siehe das offizielle Tutorial zu Wildcards . Es erklärt es gut und gibt ein Beispiel dafür, warum es notwendig ist, einfach Objekt zu verwenden.
Ben S

Antworten:

148

Eine Instanz von HashMap<String, String>Übereinstimmungen, Map<String, ?>aber nicht Map<String, Object>. Angenommen, Sie möchten eine Methode schreiben, die Karten von Strings zu irgendetwas akzeptiert : Wenn Sie schreiben würden

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

Sie können keine liefern HashMap<String, String>. Wenn du schreibst

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

Es klappt!

Eine Sache, die in Javas Generika manchmal missverstanden wird, List<String>ist, dass es sich nicht um einen Subtyp von handelt List<Object>. (Aber String[]ist in der Tat ein Subtyp von Object[], das ist einer der Gründe, warum Generika und Arrays nicht gut mischen. (Arrays in Java sind kovariant, Generika sind nicht, sie sind invariant )).

Beispiel: Wenn Sie eine Methode schreiben möchten, die Lists von InputStreams und Untertypen von akzeptiert InputStream, würden Sie schreiben

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

Übrigens: Joshua Blochs effektives Java ist eine hervorragende Ressource, wenn Sie die nicht so einfachen Dinge in Java verstehen möchten. (Ihre Frage oben wird auch im Buch sehr gut behandelt.)

Johannes Weiss
quelle
1
Ist dies der richtige Weg, um ResponseEntity <?> auf Controller-Ebene für alle meine Controller-Funktionen zu verwenden?
Irakli
makellose Antwort Johannes!
Gaurav
36

Eine andere Möglichkeit, über dieses Problem nachzudenken, ist die folgende

HashMap<String, ?> hash1;

ist äquivalent zu

HashMap<String, ? extends Object> hash1;

Koppeln Sie dieses Wissen mit dem "Get and Put-Prinzip" in Abschnitt (2.4) von Java Generics and Collections :

Das Get and Put-Prinzip: Verwenden Sie einen erweiterten Platzhalter, wenn Sie nur Werte aus einer Struktur abrufen, verwenden Sie einen Super-Platzhalter, wenn Sie nur Werte in eine Struktur einfügen, und verwenden Sie keinen Platzhalter, wenn Sie sowohl abrufen als auch setzen.

und der Platzhalter macht hoffentlich mehr Sinn.

Julien Chastang
quelle
1
Wenn "?" verwirrt Sie, "? erweitert Objekt" wird Sie wahrscheinlich mehr verwirren. Vielleicht.
Michael Myers
Der Versuch, "Denkwerkzeuge" bereitzustellen, damit man über dieses schwierige Thema nachdenken kann. Zusätzliche Informationen zum Erweitern von Platzhaltern.
Julien Chastang
2
Danke für die zusätzlichen Infos. Ich verdaue es immer noch. :)
Skiphoppy
HashMap<String, ? extends Object> so verhindert es nur null, in die Hashmap hinzugefügt zu werden?
Mallaudin
12

Es ist leicht zu verstehen, wenn Sie sich daran erinnern, dass dies Collection<Object>nur eine generische Sammlung ist, die Objekte vom Typ enthält Object, aber Collection<?>ein Supertyp aller Arten von Sammlungen ist.

Spitzenkoch
quelle
1
Es ist darauf hinzuweisen, dass dies nicht gerade einfach ist ;-), aber es ist richtig.
Sean Reilly
6

Die Antworten über der Kovarianz decken die meisten Fälle ab, übersehen jedoch eines:

"?" schließt "Objekt" in die Klassenhierarchie ein. Man könnte sagen, dass String eine Art von Objekt ist und Object eine Art von? Nicht alles passt zum Objekt, aber alles passt?

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
quelle
4

Sie können nichts sicher eingeben Map<String, ?>, da Sie nicht wissen, welchen Typ die Werte haben sollen.

Sie können jedes Objekt in ein Objekt einfügen Map<String, Object>, da der Wert als bekannt ist Object.

erickson
quelle
"Sie können nichts sicher in Map <String,?> Einfügen." False. Sie können, das ist sein Zweck.
Ben S
3
Ben ist falsch, der einzige Wert, den Sie in eine Sammlung vom Typ <?> Einfügen können, ist null, während Sie alles in eine Sammlung vom Typ <Object> einfügen können.
sk.
2
Über den Link, den ich in meiner Antwort gegeben habe: "Da wir nicht wissen, wofür der Elementtyp von c steht, können wir ihm keine Objekte hinzufügen." Ich entschuldige mich für die Fehlinformationen.
Ben S
1
Das größte Problem ist, dass ich nicht verstehe, wie es möglich ist, dass Sie HashMap <String,?> nichts hinzufügen können, wenn wir bedenken, dass ALLES außer Primitiven Objekte sind. Oder ist es für Primitive?
Avalon
1
@avalon An anderer Stelle gibt es einen Verweis auf diese Karte, die begrenzt ist. Zum Beispiel könnte es ein sein Map<String,Integer>. Nur IntegerObjekte sollten als Werte in der Karte gespeichert werden. Aber da Sie den Typ des Wertes nicht wissen (es ist ?), weiß man nicht , ob , wenn es sicher ist zu nennen put(key, "x"), put(key, 0)oder irgendetwas anderes.
Erickson
2

Das Deklarieren hash1als HashMap<String, ?>diktiert, dass die Variable hash1jeden enthalten kann HashMap, der einen Schlüssel Stringund einen beliebigen Werttyp hat.

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

Alle oben genannten Punkte sind gültig, da die Variable map jede dieser Hash-Maps speichern kann. Dieser Variablen ist es egal, um welchen Werttyp es sich bei der darin enthaltenen Hashmap handelt.

Hat eine Wildcard Having nicht aber lassen Sie jede Art von Objekt in Ihre Karte setzen. Tatsächlich können Sie mit der obigen Hash-Map mit der mapVariablen nichts einfügen:

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

Alle oben genannten Methodenaufrufe führen zu einem Fehler bei der Kompilierung, da Java den Wertetyp der darin enthaltenen HashMap nicht kennt map.

Sie können immer noch einen Wert aus der Hash-Map abrufen. Obwohl Sie "den Typ des Werts nicht kennen" (weil Sie nicht wissen, welcher Typ von Hash-Map in Ihrer Variablen enthalten ist), können Sie sagen, dass alles eine Unterklasse von Objectund, was auch immer Sie aus der Map herausholen, ist wird vom Typ Objekt sein:

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);

Der obige Codeblock gibt 10 auf der Konsole aus.


Verwenden HashMapSie zum Abschluss ein mit Platzhaltern, wenn Sie sich nicht darum kümmern (dh es spielt keine Rolle), um welche Typen HashMapes sich handelt, zum Beispiel:

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

Andernfalls geben Sie die Typen an, die Sie benötigen:

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

Bei der obigen Methode müssten wir wissen, dass der Schlüssel der Karte ein ist Character, andernfalls würden wir nicht wissen, welchen Typ wir verwenden sollen, um Werte daraus zu erhalten. Alle Objekte haben jedoch eine toString()Methode, sodass die Karte für ihre Werte einen beliebigen Objekttyp haben kann. Wir können die Werte immer noch drucken.

Kröw
quelle