Interfacce Generiche in Java
- Le interfacce generiche in Java consentono di definire interfacce che possono operare su diversi tipi di dati, migliorando la flessibilità e la riusabilità del codice.
- Le interfacce generiche sono dichiarate con parametri di tipo, simili alle classi generiche.
- Le interfacce generiche possono essere implementate da classi generiche, e i parametri di tipo devono essere coerenti tra l'interfaccia e la classe che la implementa.
- Le interfacce generiche possono specificare vincoli sui tipi di dati che possono essere utilizzati, ad esempio richiedendo che un tipo implementi un'interfaccia specifica.
- Le interfacce generiche possono essere utilizzate per creare metodi generici che operano su diversi tipi di classi, migliorando la flessibilità e la riusabilità del codice.
Interfacce Generiche
Oltre alle classi e ai metodi generici, possiamo anche avere interfacce generiche.
Le interfacce generiche sono specificate proprio come le classi generiche.
Vediamo un esempio. Creiamo un'interfaccia chiamata MinMax
che dichiara i metodi min()
e max()
, che dovrebbero restituire il valore minimo e massimo di un insieme di oggetti.
// Un esempio di interfaccia generica.
// Un'interfaccia Min/Max.
interface MinMax<T extends Comparable<T>> {
T min();
T max();
}
// Ora, implementiamo MinMax
class MiaClasse<T extends Comparable<T>> implements MinMax<T> {
T[] valori;
MiaClasse(T[] o) {
valori = o;
}
// Restituisce il valore minimo dell'array valori
public T min() {
T v = valori[0];
for(int i=1; i < valori.length; i++)
if(valori[i].compareTo(v) < 0)
v = valori[i];
return v;
}
// Restituisce il valore massimo in valori.
public T max() {
T v = valori[0];
for(int i=1; i < valori.length; i++)
if(valori[i].compareTo(v) > 0)
v = valori[i];
return v;
}
}
class DemoInterfacciaGenerica {
public static void main(String[] args) {
Integer[] numeriInteri = {3, 6, 2, 8, 6 };
Character[] caratteri = {'b', 'r', 'p', 'w' };
MiaClasse<Integer> oggettoInteger =
new MiaClasse<Integer>(numeriInteri);
MiaClasse<Character> oggettoCharacter =
new MiaClasse<Character>(caratteri);
System.out.println("Valore massimo in numeriInteri: " +
oggettoInteger.max());
System.out.println("Valore minimo in numeriInteri: " +
oggettoInteger.min());
System.out.println("Valore massimo in caratteri: " +
oggettoCharacter.max());
System.out.println("Valore minimo in caratteri: " +
oggettoCharacter.min());
}
}
L'output è mostrato qui:
Valore massimo in numeriInteri: 8
Valore minimo in numeriInteri: 2
Valore massimo in caratteri: w
Valore minimo in caratteri: b
Sebbene la maggior parte degli aspetti di questo programma dovrebbe essere facile da comprendere, è necessario evidenziare un paio di punti chiave.
Prima di tutto, notiamo che MinMax
è dichiarata così:
interface MinMax<T extends Comparable<T>> {
T min();
T max();
}
In generale, un'interfaccia generica è dichiarata nello stesso modo di una classe generica. In questo caso, il parametro di tipo è T
, e il suo limite superiore è Comparable
. Come spiegato nelle lezioni precedenti, Comparable
è un'interfaccia definita da java.lang
che specifica come gli oggetti vengono confrontati. Il suo parametro di tipo specifica il tipo degli oggetti che vengono confrontati.
Successivamente, MinMax
è implementata da MiaClasse
. Notiamo la dichiarazione di MiaClasse
, mostrata qui:
class MiaClasse<T extends Comparable<T>> implements MinMax<T> {
Prestiamo particolare attenzione al modo in cui il parametro di tipo T
è dichiarato da MiaClasse
e poi passato a MinMax
. Poiché MinMax
richiede un tipo che implementa Comparable
, la classe che implementa (MiaClasse
in questo caso) deve specificare lo stesso limite. Inoltre, una volta che questo limite è stato stabilito, non c'è bisogno di specificarlo nuovamente nella clausola implements
. Infatti, sarebbe sbagliato farlo. Ad esempio, questa riga è incorretta e non compilerà:
// Questo è sbagliato!
class MiaClasse<T extends Comparable<T>>
implements MinMax<T extends Comparable<T>> {
Una volta che il parametro di tipo è stato stabilito, viene semplicemente passato all'interfaccia senza ulteriori modifiche.
In generale, se una classe implementa un'interfaccia generica, allora quella classe deve anche essere generica, almeno nella misura in cui prende un parametro di tipo che viene passato all'interfaccia. Ad esempio, il seguente tentativo di dichiarare MiaClasse
causa un errore di compilazione:
// Errore: MiaClasse non è generica.
class MiaClasse implements MinMax<T> {
Poiché MiaClasse
non dichiara un parametro di tipo, non c'è modo di passarne uno a MinMax
. In questo caso, l'identificatore T
è semplicemente sconosciuto, e il compilatore riporta un errore. Naturalmente, se una classe implementa un tipo specifico di interfaccia generica, come mostrato qui:
// OK: MiaClasse non è generica, ma implementa MinMax<Integer>.
class MiaClasse implements MinMax<Integer> {
allora la classe che implementa non ha bisogno di essere generica.
Un'interfaccia generica offre due benefici. Prima di tutto, può essere implementata per diversi tipi di dati. Secondo, ci permette di mettere vincoli (cioè, limiti) sui tipi di dati per i quali l'interfaccia può essere implementata. Nell'esempio MinMax
, solo i tipi che implementano l'interfaccia Comparable
possono essere passati a T
.
Ecco la sintassi generalizzata per un'interfaccia generica:
interface nome_interfaccia<lista_parametri_tipo> { // …
Qui, lista_parametri_tipo
è una lista di parametri di tipo separata da virgole. Quando un'interfaccia generica è implementata, dobbiamo specificare gli argomenti di tipo, come mostrato qui:
class nome_classe<lista_parametri_tipo>
implements nome_interfaccia<lista_argomenti_tipo> {
// corpo della classe
}