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
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.