Supertypetokens in Java Generics

1. Overzicht

In deze zelfstudie gaan we vertrouwd raken met supertypetokens en zien hoe ze ons kunnen helpen om algemene typegegevens tijdens runtime te behouden.

2. Het wissen

Soms moeten we bepaalde type-informatie naar een methode overbrengen. Hier verwachten we bijvoorbeeld van Jackson dat hij de JSON-byte-array converteert naar een Draad:

byte [] data = // haal json ergens vandaan String json = objectMapper.readValue (data, String.class);

We communiceren deze verwachting via een letterlijke klassetoken, in dit geval de String.class.

We kunnen echter niet zo gemakkelijk dezelfde verwachting instellen voor generieke typen:

Map json = objectMapper.readValue (data, Map.class); // zal niet compileren

Java wist algemene typegegevens tijdens het compileren. Daarom generieke typeparameters zijn slechts een artefact van de broncode en zullen tijdens runtime ontbreken.

2.1. Reificatie

Technisch gezien zijn de generieke typen in Java niet reified. In de terminologie van de programmeertaal, wanneer een type aanwezig is tijdens runtime, zeggen we dat dat type reified is.

De reified types in Java zijn als volgt:

  • Eenvoudige primitieve typen zoals lang
  • Niet-generieke abstracties zoals Draad of Runnable
  • Ruwe soorten zoals Lijst of Hash kaart
  • Generieke typen waarin alle typen onbegrensde jokertekens zijn, zoals Lijst of Hash kaart
  • Arrays van andere reified types zoals String [], int [], lijst [], of Kaart[]

Daarom kunnen we zoiets niet gebruiken Kaart. Klasse omdat de Kaart is geen reified type.

3. Supertypetoken

Het blijkt dat we kunnen profiteren van de kracht van anonieme innerlijke klassen in Java om de type-informatie tijdens het compileren te behouden:

openbare abstracte klasse TypeReference {privé definitief Type type; openbare TypeReference () {Type superklasse = getClass (). getGenericSuperclass (); type = ((ParameterizedType) superklasse) .getActualTypeArguments () [0]; } public Type getType () {retourtype; }}

Deze klasse is abstract, dus we kunnen er alleen subklassen uit afleiden.

We kunnen bijvoorbeeld een anoniem innerlijk creëren:

TypeReference token = nieuw TypeReference() {};

De constructor voert de volgende stappen uit om de type-informatie te behouden:

  • Ten eerste krijgt het de generieke superklasse metadata voor deze specifieke instantie - in dit geval is de generieke superklasse TypeReference
  • Vervolgens wordt de feitelijke typeparameter voor de generieke superklasse opgehaald en opgeslagen - in dit geval zou dat zo zijn Kaart

Deze benadering voor het behouden van de generieke type-informatie is gewoonlijk bekend als super type token:

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

Met behulp van supertypetokens weten we dat het containertype Kaart, en ook zijn de typeparameters Draad en Geheel getal.

Dit patroon is zo beroemd dat bibliotheken zoals Jackson en frameworks zoals Spring er hun eigen implementaties van hebben. Het parseren van een JSON-object in een Kaart kan worden bereikt door dat type te definiëren met een supertypetoken:

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

4. Conclusie

In deze zelfstudie hebben we geleerd hoe we supertypetokens kunnen gebruiken om de algemene typegegevens tijdens runtime te behouden.

Zoals gewoonlijk zijn alle voorbeelden beschikbaar op GitHub.