Java Generics – Un nuovo modo per la gestione dei tipi in Java®
Introdotti dalla release 5.0 i generics consentono di definire un processo mediante il quale è possibile “parametrizzare” il tipo di classi, interfacce e metodi, similmente a quanto avviene nel caso dei parametri formali che caratterizzano le firme dei metodi allo stesso modo i generics mettono a disposizione dello sviluppatore i mezzi per utilizzare lo stesso codice con input differenti; la differenza e che l’input per i parametri formali sono dei valori, mentre nel caso dei generics questi parametri sono dei tipi.
Le soluzioni che utilizzano i generics traggono diversi vantaggi rispetto ai codici sviluppati secondo l’approccio standard, in particolare:
- Controllo dei tipi rafforzato a tempo di compilazione: l’uso dei generics impone un controllo preventivo per verificare la validità per i tipi utilizzati;
- Eliminazione del cast: l’uso dei generics infatti impone una preventiva indicazione circa il tipo di dato utilizzato in una struttura dati o un metodo, il che porta all’impossibilità di utilizzare ipi non conformi con la specifica e a non avere informazioni circa il tipo di oggetto ottenibile attraverso la struttura/metodo;
- Possibilità di implementare algoritmi generici: in pratica si consente allo sviluppatore di definire algoritmi che operano su di una collezione di tipi differenti che sono comunque sicuri da un punto di vista dei tipi, inoltre fattore non trascurabile la leggibilità del codice ne trae grande benefico.
Dopo questa breve introduzione cominciamo ad entrare più nel merito dell’argomento generics.
Un tipo generico e una classe o un’interfaccia parametrizzata rispetto ai tipi che essa gestisce, a tal proposito a titolo esemplificativo si consideri il seguente esempio di codice:
public class Media {private Object media;
public void set(Object media) { this.media=media; } public Object getMedia() { return this.media; } } |
Così come sono stati definiti accettano e restituisco degli oggetti, il che quindi ci da la possibilità di utilizzare qualunque tipo noi si desideri escludendo ovviamente i tipi primitivi, osserviamo però che a tempo di compilazione non è possibile sapere in che modo la classe sarà utilizzata, questo ad esempio significa che in una certa parte del codice Media sarà utilizzato con una generica classe libro, mentre in un altro punto del codice potrebbe essere utilizzato con un String, il che potrebbe determinare errori a tempo di esecuzione, per risolvere questo tipo di probelma si ricorre ai generics.
Questi nel caso di una classe ne modificano la definizione nel modo seguente:
class NomeClasse<T1,T2,…,Tn>
La sezione racchiusa tra le parentesi angolari di consente di specificare i parametri di tipo, chiamati anche varabili di tipo, notiamo come nel caso della specifica sia possibile indicare più variabili di tipo, tal proposito si consideri un HashMap la cui definizione prevede un tipo per la chiave ed un per il valore.
Detto questo la dichiarazione precedente per la classe Media si modifica nel modo seguente:
public class Media<T> {// La “T” in questo caso indica Tipo
private T media; public void set(T media) { this.media=media; } public T get() { return this.media; } } |
Osserviamo come nella nuova definizione ogni occorrenza di Object sia stata sostituita dal tipo generico T, il quale, così come già avveniva per la versione precedente, può essere sostituito solo con un tipo non primitivo, il che significa la possibilità di utilizzare:
- Tipi classe;
- Interfacce;
- Array;
- Altre variabili di tipo.
Detto questo prima di passare oltre è opportuno fare una breve digressione circa il sistema di nomenclatura utilizzata nel caso dei generics, in primis le variabili di tipo vanno sempre specificate con una singola lettera maiuscola, il che pone questa convenzione i netto contrasto con lo schema per le nomenclature utilizzate i Java, questa scelta è però motivata dalla necessità di evitare inutili ambiguità tra variabili di tipi, classi ed interfacce; chiarito questo aspetto in genere per i parametri di tipo si utilizzano le seguenti convenzioni:
- E: Rappresenta un elemento, è utilizzato in modo intensivo nel caso delle collection;
- K: in genere è utilizzato per identificare una chiave;
- N: numeri
- T: tipo
- V: valori
- S,U,V …: utilizzate per identificare la 2°,3°, 4° variabile di tipo.
Ritornando ora alla nostra classe Media, vediamo in che modo essa è istanziabile, nel caso dei generics questa operazione è anche chiamata invocazione con tipo generico, nel caso del nostro esempio ipotizziamo di volere sostituire T con un generico tipo Libro, dove Libro è una classe definita in precedenza:
Media<Libro> mediaLibro;
Si potrebbe essere portati a pensare, osservando la precedente dichiarazione, che l’invocazione tramite varabili di tipo sia simile ad un invocazione di un metodo, la differenza è però sostanziale se in fatti nel secondo caso forniamo dei valori, nel primo si da una precisa indicazione circa il tipo della classe, il che ne determinerà in maniera evidente il comportamento.
La dichiarazione precedente non è però sufficiente a creare l’oggetto di cui abbiamo bisogno, essa infatti si limita a dichiarare una variabile al cui interno sarà memorizzato un reference ad un Media di tipo libro, nulla più, ora così come avviene per qualunque oggetto il passo successivo è quello dell’instanziazione che nel caso dei genirics si risolve nello scrivere un’istruzione simile al seguente:
nomeVariabile= new NomeClasse<Tipo>();
questo statement nel caso del nostro esempio si traduce nel seguente comando:
mediaLibro = new Media<Libro>();
In realtà la dichiarazione precedente a partire dalla release 7 di Java è stata sostituta dalla seguente forma semplificata:
mediaLibro = new Media<>();
In questo caso il tipo per Media viene determinato per mezzo di un processo di type inference di cui ci occuperemo nel seguito.
Come abbiamo osservato in precedenza nella dichiarazione di una classe/metodo/interfaccia è possibile specificare più variabili di tipo a tale proposito si consideri questo semplice esempio nel quale si definisce un’interfaccia in grado di operare su due parametri:
public interface Coppia<K,V> {public getChiave();
public getValore(); } public class ReferenceLibro<K,V> implements Coppia<K,V> { private K codiceDiIntenificazione; private V titolo; public RefernceLibro(K codice, V valore) { this.codiceDiIdentificazione = codice; this.titolo = titolo; } public K getChiave() { return this.codiceDiIdentificazione; } public V getValore() { return this.titolo; } } |
A questo punto sulla base della dichiarazione precedente è possibile istanziare la seguente tripla di oggetti:
ReferenceLibro<String,String> oggettoISBN =new REferenceLibro<String,String>(“978-0-07-178942-3”,“OCA Java SE 7 Exam 1Z0-803”);
ReferenceLibro<File,String> oggettoQR = new ReferenceLibro(new File(“qr_libro.png”), “OCA Java SE 7 Exam 1Z0-803”); REferenceLibro<Integer,String> oggettoIntero = new ReferenceLibro<Integer,String>(12345, “OCA Java SE 7 Exam 1Z0-803”); |
Nel caso dell’ultima dichiarazione i noti come sia compito del compilatore effettuare l’operazione di autoboxing (si “wrappa” il tipo primitivo int all’interno di un oggetto Integer) per la varabile di tipo intero; come accennato in precedenza a partire da Java 7 è possibile fare uso di una versione compatta per le dichiarazioni che fanno uso dei generics, nel nostro caso quindi i tre precedenti comandi possono essere così formalizzati:
ReferenceLibro<String,String> oggettoISBN =new ReferenceLibro<>(“978-0-07-178942-3”,“OCA Java SE 7 Exam 1Z0-803”);
ReferenceLibro<File,String> oggettoQR = new ReferenceLibro<>(new File(“qr_libro.png”), “OCA Java SE 7 Exam 1Z0-803”); REferenceLibro<Integer,String> oggettoIntero = new ReferenceLibro<>(12345, “OCA Java SE 7 Exam 1Z0-803”); |
Per quel concerne la creazione di interfacce “generiche” i deve seguire lo stesso approccio utilizzato nel caso delle classi.
Dagli esempi precedenti si potrebbe concludere che nel processo di creazione di un nuovo oggetto si può fare uso solo di classi già definite, in realtà come abbiamo accennato in precedenza l’uso dei generics ci consente di specificare come variabile di tipo un’altra variabile di tipo come risulta dal prossimo esempio:
ReferenceLibro<String,Media<Libro>> oggettoISBN = new Referencelibro< >(“978-0-07-178942-3”,new Media<Libro>(….)); |
Con quest’ultimo esempio si conclude il nostro primo articolo relativo ai tipi generici in java, nel prossimo affronteremo le problematiche connesse alla definizione dei metodi generici.
Stay tuned.
Alessandro Grande
Commenti