Restrizioni sui Generics in Java
Ci sono alcune restrizioni che dobbiamo tenere a mente quando utilizziamo i tipi generici in Java.
Esse riguardano la creazione di oggetti di un parametro di tipo, membri statici, eccezioni e array. In questa lezione, esploreremo queste restrizioni e come influenzano il design del codice.
- I parametri di tipo non possono essere istanziati direttamente.
- I membri
static
non possono utilizzare i parametri di tipo della classe che li racchiude. - Non è possibile creare un array il cui tipo di elemento è un parametro di tipo.
- Le classi generiche non possono estendere
Throwable
, quindi non è possibile creare eccezioni generiche.
I Parametri di Tipo Non Possono Essere Istanziati
Una prima restrizione sull'uso dei tipi generici riguarda il fatto che non è possibile creare un'istanza di un parametro di tipo.
Ad esempio, consideriamo questa classe:
// Non si può creare un'istanza di T.
class Gen<T> {
// Dichiarazione di un oggetto di tipo T.
T og;
Gen() {
// Istruzione illegale!
og = new T();
}
}
Qui, è illegale tentare di creare un'istanza di T
. Il motivo dovrebbe essere facile da capire: il compilatore non sa che tipo di oggetto creare. T
è semplicemente un segnaposto.
In realtà, la motivazione deriva da come funziona la cancellazione o erasure dei generics che abbiamo discusso nelle lezioni precedenti. Durante la cancellazione, il compilatore sostituisce T
con Object
, quindi l'istruzione og = new T();
diventa og = new Object();
, che non è ciò che intendiamo.
Per cui, ad esempio, se creiamo un'istanza di Gen
come segue:
Gen<String> oggetto = new Gen<String>();
il compilatore trasforma il codice del costruttore in:
og = new Object();
quindi non sta creando una stringa, ma un oggetto di tipo Object
.
Restrizioni sui Membri static
Nessun membro static
può utilizzare un parametro di tipo dichiarato dalla classe che lo racchiude.
Ad esempio, entrambi i membri static
di questa classe sono illegali:
class Sbagliata<T> {
// Errore: nessuna variabile static di tipo T.
static T ogg;
// Errore: nessun metodo static può utilizzare T.
static T getOgg() {
return ogg;
}
}
Per capire perché, dobbiamo ritornare alla cancellazione dei generics e vedere cosa succede se fosse consentito.
Se fosse permesso, il compilatore trasformerebbe il codice in qualcosa del genere:
class Sbagliata {
static Object ogg;
static Object getOgg() {
return ogg;
}
}
Ora, tuttavia, poiché ogg
è static
così come getOgg()
, non è più legato a un'istanza specifica di Sbagliata
. Quindi, non ha senso che sia di tipo T
, perché T
è un parametro di tipo legato a un'istanza specifica della classe.
Per cui, se istanziamo due oggetti di Sbagliata
con tipi diversi, entrambi i membri static
farebbero riferimento allo stesso membro static
, il che non avrebbe senso:
Sbagliata<Integer> oggettoI = new Sbagliata<Integer>();
Sbagliata<String> oggettoS = new Sbagliata<String>();
// Quale oggetto otteniamo qui?
Object o = Sbagliata.getOgg();
Per evitare questa confusione, Java non consente ai membri static
di utilizzare i parametri di tipo della classe che li racchiude.
Tuttavia, è possibile utilizzare i parametri di tipo nei membri static
di classi sia generiche che non generiche, come mostrato qui:
class Corretta<T> {
static <V> V metodoStatico(V v) {
/* ... */
}
/* ... */
}
In questo caso, il metodo metodoStatico()
è static
e utilizza un parametro di tipo V
, che non è legato al parametro di tipo T
della classe Corretta
. Questo è legale perché V
è un parametro di tipo del metodo, non della classe.
A questo punto possiamo invocare metodoStatico()
come segue:
Corretta.<Integer>metodoStatico(10);
Corretta.<String>metodoStatico("Test");
Restrizioni sugli Array Generici
Ci sono due importanti restrizioni dei generics che si applicano agli array.
Prima restrizione: non è possibile istanziare un array il cui tipo di elemento è un parametro di tipo.
Seconda restrizione: non è possibile creare un array di riferimenti generici tipo-specifici.
Il seguente breve programma mostra entrambe le situazioni:
// Generics e array.
class Gen<T extends Number> {
T oggetto;
// Dichiarazione di un array di tipo T.
// Qui è legale dichiarare un array di tipo T.
T[] valori;
Gen(T o, T[] numeri) {
oggetto = o;
// Questa istruzione è illegale.
// Non si può creare un array di T.
// valori = new T[10];
// Ma, questa istruzione è OK.
// Assegna un riferimento ad un array esistente.
valori = numeri;
}
}
class ArrayGenerici {
public static void main(String[] args) {
Integer[] n = { 1, 2, 3, 4, 5 };
Gen<Integer> oggettoI = new Gen<Integer>(50, n);
// Errore: Non si può creare un array
// di riferimenti generici tipo-specifici.
// Gen<Integer>[] generici = new Gen<Integer>[10];
// Ma, questa istruzione è OK.
// Si può creare un array di riferimenti generici con wildcard.
Gen<?>[] generici = new Gen<?>[10]; // OK
}
}
Come mostra il programma, è valido dichiarare un riferimento ad un array di tipo T
, come fa questa riga:
T[] valori; // OK
Ma, non è possibile istanziare un array di T
, come tenta questa linea commentata:
// Questa istruzione è illegale.
// Non si può creare un array di T.
// valori = new T[10];
La ragione per cui non è possibile creare un array di T
è che non c'è modo per il compilatore di sapere che tipo di array creare effettivamente.
Tuttavia, è possibile passare un riferimento ad un array compatibile con T
a Gen() quando un oggetto viene creato e assegnare quel riferimento a valori, come fa il programma in questa riga:
// Assegna un riferimento ad un array esistente.
valori = numeri;
Questo funziona perché l'array passato a Gen
ha un tipo conosciuto, che sarà lo stesso tipo di T
al momento della creazione dell'oggetto.
All'interno di main()
, notiamo che non è possibile dichiarare un array di riferimenti ad un tipo generico specifico. Cioè, questa linea
// Errore: Non si può creare un array
// di riferimenti generici tipo-specifici.
// Gen<Integer>[] generici = new Gen<Integer>[10];
non compilerà.
È possibile creare un array di riferimenti ad un tipo generico se utilizziamo un wildcard, tuttavia, come mostrato qui:
// Si può creare un array di riferimenti generici con wildcard.
Gen<?>[] generici = new Gen<?>[10]; // OK
Questo approccio è migliore dell'usare un array di tipi raw, perché almeno qualche controllo di tipo sarà ancora applicato.
Restrizione delle Eccezioni Generiche
Una classe generica non può estendere Throwable
. Questo significa che non è possibile creare classi di eccezione generiche.
Per esempio, questa classe non è valida:
// ERRORE
// Non è possibile estendere Throwable con un tipo generico.
class EccezioneGenerica<T> extends Throwable {
T valore;
EccezioneGenerica(T v) {
valore = v;
}
@Override
public String getMessage() {
return "Valore: " + valore;
}
}
Il motivo per cui non è possibile estendere Throwable
con un tipo generico è che le eccezioni devono essere istanziate con un tipo specifico al momento del lancio. Poiché i generics sono cancellati durante la compilazione, non c'è modo di garantire che il tipo specifico sia disponibile al momento dell'istanza dell'eccezione.