Java Generics: Generischer Typ, der nur als Rückgabetyp definiert ist

68

Ich schaue mir einen GXT-Code für GWT an und bin auf diese Verwendung von Generika gestoßen, für die ich in den Java-Tutorials kein weiteres Beispiel finden kann. Der Klassenname lautet, com.extjs.gxt.ui.client.data.BaseModelDatawenn Sie den gesamten Code anzeigen möchten. Hier sind die wichtigen Teile:

private RpcMap map;

public <X> X get(String property) {
  if (allowNestedValues && NestedModelUtil.isNestedProperty(property)) {
    return (X)NestedModelUtil.getNestedValue(this, property);
  }
  return map == null ? null : (X) map.get(property);
}

Xwird nirgendwo anders in der Klasse oder irgendwo in der Hierarchie definiert, und wenn ich in Eclipse auf "Gehe zur Deklaration" drücke, gehe ich einfach zur <X>Signatur in der öffentlichen Methode.

Ich habe versucht, diese Methode mit den folgenden zwei Beispielen aufzurufen, um zu sehen, was passiert:

public Date getExpiredate() {
    return  get("expiredate");
}

public String getSubject() {
    return  get("subject");
}

Sie kompilieren und zeigen keine Fehler oder Warnungen. Ich würde zumindest denken, dass ich eine Besetzung machen müsste, um dies zum Laufen zu bringen.

Bedeutet dies, dass Generika einen magischen Rückgabewert zulassen, der alles sein kann und nur zur Laufzeit explodiert? Dies scheint dem zu widersprechen, was Generika tun sollen. Kann mir jemand dies erklären und mir möglicherweise einen Link zu einer Dokumentation geben, die dies etwas besser erklärt? Ich habe das 23-seitige PDF von Sun zu Generika durchgesehen und jedes Beispiel für einen Rückgabewert wird entweder auf Klassenebene oder in einem der übergebenen Parameter definiert.

Jason Tholstrup
quelle
Danke für die bisherigen Antworten! Ich würde euch alle positiv bewerten, wenn ich den Repräsentanten hätte (besonders saua, sehr hilfreich). Sehr beeindruckt von der Bearbeitungszeit. Ich würde immer noch gerne einige Referenzen sehen, die dies etwas detaillierter erklären könnten, aber ich denke, ich verstehe das viel besser als zuvor
Jason Tholstrup
Bitte aktualisieren Sie den Link: extjs.com/deploy/gxtdocs/com/extjs/gxt/ui/client/data/… - kaputt
Mr_and_Mrs_D
Fühlen Sie sich frei, es zu aktualisieren. Ich habe gxt seit einigen Jahren nicht mehr benutzt.
Jason Tholstrup
Einige Methoden von FXMLLoader nutzen dies ebenfalls. Dies ist besonders hilfreich, da eine Typumwandlung ohnehin immer vom Benutzer durchgeführt wird. Wäre schön, wenn dieses Verhalten ebenfalls hinzugefügt würde ObjectInputStream::readObject.
Mordechai

Antworten:

55

Die Methode gibt einen Typ von dem zurück, was Sie erwarten ( <X>ist in der Methode definiert und absolut unbegrenzt).

Dies ist sehr, sehr gefährlich, da nicht vorgesehen ist, dass der Rückgabetyp tatsächlich mit dem zurückgegebenen Wert übereinstimmt.

Der einzige Vorteil besteht darin, dass Sie den Rückgabewert solcher generischen Suchmethoden, die einen beliebigen Typ zurückgeben können, nicht umwandeln müssen.

Ich würde sagen: Verwenden Sie solche Konstrukte mit Vorsicht, da Sie so gut wie alle Typensicherheit verlieren und nur gewinnen, dass Sie nicht bei jedem Aufruf eine explizite Besetzung schreiben müssen get().

Und ja: Das ist so ziemlich schwarze Magie, die zur Laufzeit explodiert und die gesamte Idee bricht, was Generika erreichen sollen.

Joachim Sauer
quelle
6
Dieses Konstrukt ist viel sicherer, wenn der Typ auch zum Qualifizieren eines generischen Typs in einer der Methoden verwendet wird. Dies bedeutet, dass Sie nicht explizit umwandeln müssen. Dann besteht keine Chance, zur Laufzeit in die Luft zu jagen.
Bill Michell
3
Im Fall von GWT ist es möglich, dass dies beabsichtigt war; GWT kompiliert zu JavaScript, das bekanntlich dynamisch und schwach typisiert ist.
Nicole
39

Der Typ wird in der Methode deklariert. Das <X>heißt " ". Der Typ ist dann nur auf die Methode beschränkt und für einen bestimmten Aufruf relevant. Der Grund, warum Ihr Testcode kompiliert wird, ist, dass der Compiler versucht, den Typ zu bestimmen, und sich nur beschwert, wenn dies nicht möglich ist. Es gibt Fälle, in denen Sie explizit sein müssen.

Zum Beispiel für die Erklärung Collections.emptySet()ist

public static final <T> Set<T> emptySet()

In diesem Fall kann der Compiler Folgendes erraten:

Set<String> s = Collections.emptySet();

Wenn dies nicht möglich ist, müssen Sie Folgendes eingeben:

Collections.<String>emptySet();
sblundy
quelle
Randnotiz: Neuere Versionen von Java (8+) sind mit Typabzug intelligenter geworden, und Beispiele wie das letzte benötigen keinen expliziten Typ mehr (je nachdem, wo sie verwendet werden).
Joachim Sauer
5

Ich habe nur versucht, dasselbe mit einer GXT-Klasse herauszufinden. Insbesondere habe ich versucht, eine Methode mit der Signatur aufzurufen:

class Model {
    public <X> X get(String property) { ... }
}

Um die obige Methode aus Ihrem Code aufzurufen und X in einen String umwandeln zu lassen, gehe ich wie folgt vor:

public String myMethod(Data data) {
    Model model = new Model(data);
    return model.<String>get("status");
}

Der obige Code ruft die get-Methode auf und teilt ihr mit, dass der von X zurückgegebene Typ als String zurückgegeben werden soll.

In dem Fall, in dem sich die Methode in derselben Klasse wie Sie befindet, habe ich festgestellt, dass ich sie mit einem "this" aufrufen muss. Zum Beispiel:

this.<String>get("status");

Wie andere gesagt haben, ist dies für das GXT-Team ziemlich schlampig und gefährlich.

Kyrra
quelle
2

BaseModelData löst beim Kompilieren ungeprüfte Warnungen aus, da es unsicher ist. Bei dieser Verwendung löst Ihr Code zur Laufzeit eine ClassCastException aus, obwohl er selbst keine Warnungen enthält.

public String getExpireDate() {
  return  get("expiredate");
}
erickson
quelle
1
Ok, ich habe es versucht: Es wird nur eine Ausnahme ausgelöst, wenn der Laufzeit-Typ nicht vom Typ string ist. Wenn es eine Zeichenfolge ist, ist es in Ordnung. Sie haben gerade die Besetzung auf die Methode get verschoben.
Jason Tholstrup
1
Genau. Wenn Sie Ihren Code ohne Warnungen kompilieren, garantiert generics, dass Sie niemals eine ClassCastException erhalten. Durch Ignorieren der Warnungen hat BaseModelData diese Zusicherung verworfen.
Erickson
2

Interessanter Hinweis von RpcMap (GXT API 1.2)

get's Header:

public java.lang.Object get(java.lang.Object key)

Ein generischer Parameter von <X>dort, der nicht fundiert ist, hat den gleichen Effekt, außer dass Sie nicht überall "Objekt" sagen müssen. Ich stimme dem anderen Poster zu, das ist schlampig und ein bisschen gefährlich.

Reich
quelle
0

Ja, das ist gefährlich. Normalerweise würden Sie diesen Code folgendermaßen schützen:

<X> getProperty(String name, Class<X> clazz) {
   X foo = (X) whatever(name);
   assert clazz.isAssignableFrom(foo);
   return foo;
}

String getString(String name) {
  return getProperty(name, String.class);
}

int getInt(String name) {
  return getProperty(name, Integer.class);
}
Paulmurray
quelle
2
warum ist das besser Es kann zur Laufzeit immer noch explodieren, nur auf eine andere Art und Weise.
David Roussel
3
Sprengschutz durch Einsetzen von zwei aufeinanderfolgenden Laufzeitbomben !! Zuerst wird die Besetzung als Ausnahme explodieren ..., wenn das nicht genug wäre, würde die Behauptung (die wahrscheinlich sowieso nicht ausgeführt würde) das gesamte Programm in die Luft jagen!, Mehr Komplexität ... keine zusätzlichen Vorteile ... - 1
Newtopian
3
public <T> T get (int i, Typ Class <T>) {Object val = get (i); if (type.isInstance (val)) {return type.cast (val); } return null; }
Tatarize