Generics e Errori di Ambiguità in Java
- Gli errori di ambiguità nei generics in Java si verificano quando due dichiarazioni generiche si risolvono nello stesso tipo cancellato, causando conflitti.
- Per risolvere gli errori di ambiguità, è spesso necessario ristrutturare il codice o utilizzare nomi di metodi distinti.
- La cancellazione dei generics può portare a situazioni in cui i metodi sovraccaricati diventano ambigui, specialmente quando i tipi generici sono simili o identici.
Errori di Ambiguità e Tipi Generici
L'inclusione dei generics in Java dà origine a un altro tipo di errore contro cui dobbiamo proteggerci: l'ambiguità.
Gli errori di ambiguità si verificano quando la cancellazione o erasure fa sì che due dichiarazioni generiche apparentemente distinte si risolvano nello stesso tipo cancellato, causando un conflitto.
Ecco un esempio che coinvolge l'overloading di metodi:
// Ambiguità causata dalla cancellazione su
// metodi sovraccaricati.
class MiaClasseGen<T, V> {
// Dichiarazione di due oggetti di tipo T e V.
T ogg1;
V ogg2;
/* ... */
// Questi due metodi sovraccaricati sono ambigui
// e non compileranno.
void imposta(T o) {
ogg1 = o;
}
// Questo metodo è ambiguo con il precedente
void imposta(V o) {
ogg2 = o;
}
}
Notiamo che MiaClasseGen
dichiara due tipi generici: T
e V
.
All'interno di MiaClasseGen
, viene fatto un tentativo di sovraccaricare imposta()
basato su parametri di tipo T
e V
. Questo sembra ragionevole perché T
e V
appaiono essere tipi diversi. Tuttavia, ci sono due problemi di ambiguità qui.
Primo, così come MiaClasseGen
è scritta, non c'è alcun requisito che T
e V
siano effettivamente tipi diversi. Per esempio, è perfettamente corretto (in principio) costruire un oggetto MiaClasseGen
come mostrato qui:
// In questo caso, sia T che V sono String.
MiaClasseGen<String, String> ogg =
new MiaClasseGen<String, String>()
In questo caso, sia T
che V
saranno sostituiti da String
. Questo rende entrambe le versioni di imposta()
identiche, che è, ovviamente, un errore.
Il secondo e più fondamentale problema è che la cancellazione del tipo di imposta()
riduce entrambe le versioni alla seguente:
void imposta(Object o) {
// ...
}
Quindi, il sovraccarico di imposta()
come tentato in MiaClasseGen
è intrinsecamente ambiguo.
Gli errori di ambiguità possono essere difficili da correggere. Per esempio, se sappiamo che V
sarà sempre qualche tipo di Number
, potremmo provare a correggere MiaClasseGen
riscrivendo la sua dichiarazione come mostrato qui:
// Quasi OK, ma ancora ambiguo.
class MiaClasseGen<T, V extends Number> {
Questa modifica fa sì che MiaClasseGen
compili, e possiamo persino istanziare oggetti come quello mostrato qui:
MiaClasseGen<String, Number> x =
new MiaClasseGen<String, Number>();
Questo funziona perché Java può determinare accuratamente quale metodo chiamare. Tuttavia, l'ambiguità ritorna quando proviamo questa riga:
MiaClasseGen<Number, Number> x =
new MiaClasseGen<Number, Number>();
In questo caso, poiché sia T
che V
sono Number
, quale versione di imposta()
deve essere chiamata? La chiamata a imposta()
è ora ambigua.
Francamente, nell'esempio precedente, sarebbe molto meglio usare due nomi di metodi separati, piuttosto che cercare di sovraccaricare imposta()
.
Spesso, la soluzione all'ambiguità coinvolge la ristrutturazione del codice, perché l'ambiguità spesso significa che abbiamo un errore concettuale nel nostro design.