Token di super tipo in Java Generics

1. Panoramica

In questo tutorial, acquisiremo familiarità con i token di super tipo e vedremo come possono aiutarci a preservare le informazioni di tipo generico in fase di esecuzione.

2. La cancellazione

A volte abbiamo bisogno di trasmettere informazioni di tipo particolare a un metodo . Ad esempio, qui ci aspettiamo che Jackson converta l'array di byte JSON in una stringa:

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

Stiamo comunicando questa aspettativa tramite un token di classe letterale, in questo caso, String.class.

Tuttavia, non possiamo impostare facilmente la stessa aspettativa per i tipi generici:

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

Java cancella le informazioni di tipo generico durante la compilazione. Pertanto, i parametri di tipo generico sono semplicemente un artefatto del codice sorgente e saranno assenti in fase di esecuzione.

2.1. Reificazione

Tecnicamente parlando, i tipi generici non sono reificati in Java. Nella terminologia del linguaggio di programmazione, quando un tipo è presente in fase di esecuzione, diciamo che il tipo è reificato.

I tipi reificati in Java sono i seguenti:

  • Tipi primitivi semplici come long
  • Astrazioni non generiche come String o Runnable
  • Tipi grezzi come List o HashMap
  • Tipi generici in cui tutti i tipi sono caratteri jolly illimitati come List o HashMap
  • Matrici di altri tipi reificati come String [], int [], List [] o Map []

Di conseguenza, non possiamo usare qualcosa come Map.class perché la mappa non è un tipo reificato.

3. Token di super tipo

A quanto pare, possiamo sfruttare la potenza delle classi interne anonime in Java per preservare le informazioni sul tipo durante la compilazione:

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

Questa classe è astratta, quindi possiamo solo derivarne sottoclassi.

Ad esempio, possiamo creare un interno anonimo:

TypeReference token = new TypeReference() {};

Il costruttore esegue i seguenti passaggi per preservare le informazioni sul tipo:

  • Innanzitutto, ottiene i metadati della superclasse generica per questa particolare istanza: in questo caso, la superclasse generica è TypeReference
  • Quindi ottiene e memorizza il parametro di tipo effettivo per la superclasse generica, in questo caso sarebbe Map

Questo approccio per preservare le informazioni di tipo generico è generalmente noto come token di super tipo :

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

Usando i token di super tipo, sappiamo che il tipo di contenitore è Map e inoltre i suoi parametri di tipo sono String e Integer.

Questo modello è così famoso che biblioteche come Jackson e framework come Spring ne hanno le proprie implementazioni. L'analisi di un oggetto JSON in una mappa può essere eseguita definendo quel tipo con un token di tipo super:

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

4. Conclusione

In questo tutorial, abbiamo imparato come utilizzare i token di super tipo per preservare le informazioni sul tipo generico in fase di esecuzione.

Come al solito, tutti gli esempi sono disponibili su GitHub.