Super Type Tokens in Java Generics

1. Übersicht

In diesem Tutorial werden wir uns mit Super-Typ-Token vertraut machen und sehen, wie sie uns helfen können, generische Typinformationen zur Laufzeit beizubehalten.

2. Die Löschung

Manchmal müssen wir einer Methode bestimmte Typinformationen übermitteln . Zum Beispiel erwarten wir von Jackson, dass das JSON-Byte-Array in einen String konvertiert wird :

byte[] data = // fetch json from somewhere String json = objectMapper.readValue(data, String.class);

Wir kommunizieren diese Erwartung über ein Literal-Klassentoken, in diesem Fall die String.class.

Wir können jedoch nicht so einfach die gleiche Erwartung für generische Typen festlegen:

Map json = objectMapper.readValue(data, Map.class); // won't compile

Java löscht generische Typinformationen während der Kompilierung. Daher sind generische Typparameter lediglich ein Artefakt des Quellcodes und fehlen zur Laufzeit.

2.1. Verdinglichung

Technisch gesehen werden die generischen Typen in Java nicht geändert. Wenn in der Terminologie der Programmiersprache zur Laufzeit ein Typ vorhanden ist, wird dieser Typ geändert.

Die reifizierten Typen in Java sind wie folgt:

  • Einfache primitive Typen wie lang
  • Nicht generische Abstraktionen wie String oder Runnable
  • Raw-Typen wie List oder HashMap
  • Generische Typen, bei denen alle Typen unbegrenzte Platzhalter sind, wie z. B. List oder HashMap
  • Arrays anderer reifizierter Typen wie String [], int [], List [] oder Map []

Folglich können wir so etwas wie Map.class nicht verwenden, da die Map kein reifizierter Typ ist.

3. Super Type Token

Wie sich herausstellt, können wir die Leistung anonymer innerer Klassen in Java nutzen, um die Typinformationen während der Kompilierungszeit beizubehalten:

public abstract class TypeReference { private final Type type; public TypeReference() { Type superclass = getClass().getGenericSuperclass(); type = ((ParameterizedType) superclass).getActualTypeArguments()[0]; } public Type getType() { return type; } }

Diese Klasse ist abstrakt, daher können wir nur Unterklassen daraus ableiten.

Zum Beispiel können wir ein anonymes Inneres erstellen:

TypeReference token = new TypeReference() {};

Der Konstruktor führt die folgenden Schritte aus, um die Typinformationen beizubehalten:

  • Zunächst werden die generischen Superklassen-Metadaten für diese bestimmte Instanz abgerufen. In diesem Fall lautet die generische Superklasse TypeReference
  • Dann wird der tatsächliche Typparameter für die generische Oberklasse abgerufen und gespeichert - in diesem Fall wäre es Map

Dieser Ansatz zum Beibehalten der generischen Typinformationen wird normalerweise als Supertyp-Token bezeichnet :

TypeReference token = new TypeReference() {}; Type type = token.getType(); assertEquals("java.util.Map", type.getTypeName()); Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments(); assertEquals("java.lang.String", typeArguments[0].getTypeName()); assertEquals("java.lang.Integer", typeArguments[1].getTypeName());

Bei Verwendung von Supertyp-Token wissen wir, dass der Containertyp Map ist und dass seine Typparameter String und Integer sind.

Dieses Muster ist so berühmt, dass Bibliotheken wie Jackson und Frameworks wie Spring ihre eigenen Implementierungen haben. Das Parsen eines JSON-Objekts in eine Map kann erreicht werden, indem dieser Typ mit einem Supertyp-Token definiert wird:

TypeReference token = new TypeReference() {}; Map json = objectMapper.readValue(data, token);

4. Fazit

In diesem Tutorial haben wir gelernt, wie wir Super-Typ-Token verwenden können, um die generischen Typinformationen zur Laufzeit beizubehalten.

Wie üblich sind alle Beispiele auf GitHub verfügbar.