I tipi generici in Java sono un potente meccanismo che permette di scrivere codice flessibile e riutilizzabile; dando la possibilità di definire classi, interfacce e metodi che operano su tipi di dati specificati solo al momento dell’utilizzo.
Ad esempio le collezioni sono classi generiche dove il tipo su cui operano è definito soltanto al momento dell’istanziazione attraverso l’operatore diamante<>. Questo ha permesso di utilizzare un singolo codice per creare delle collection per qualsiasi tipo, ad esempio senza i tipi generici si sarebbe dovuto difinire un tipo specifico di ArrayList per ogni tipo d’oggetto esistente.
Non è possibile utilizzare i tipi primitivi con tipi generici, questo perché durante la cancellazione del tipo, il tipo generico viene sostituito con Object e i primitivi non possono essere trattati come oggetti.
No ereditarietà dei tipi generici
Per le classi generiche non vale l’ereditarietà dei tipi generici ovvero ArrayList<Mela> non è di tipo ArrayList<Frutto> o ArrayList<Object>, quindi non possiamo fare:
ArrayList`<Frutto> listaNumeri = new ArrayList<Mela>(); //errore
Ma rimane comunque l’ereditarietà tra classi:
List`<Frutto> listaNumeri = new ArrayList<Frutto>();
No upcasting
Non è permesso l’upcasting nei tipi generici, ad esempio:
Creiamo una lista di mele
`ArrayList<Mela> mele = new ArrayList<Mela>();
Definiamo un metodo che prende in input una lista di frutti e inserisce una pera (sotto classe di frutto)
public static void aggiungiPera(`ArrayList<Frutto> frutti) { frutti.add(new Pera());}
Ora, proviamo a utilizzare la lista di mele come input del metodo appena citato.
aggiungiPera(mele);
Tuttavia, questo ci darà un errore di compilazione. Il motivo è che il metodo aggiungiPera prende in input una lista di frutti, mentre noi gli stiamo passando una lista di mele. Questo non è possibile perché non si può fare l’upcasting dei tipi generici.
Se fosse possibile, avremmo come risultato una lista di mele che contiene una pera, il che non è corretto. La lista di mele dovrebbe contenere solo mele, non frutti di tipo diverso.
oss: Le collection (come ArrayList) sono generiche.
Tuttavia, possiamo l’operatore jolly? con ? extends Frutto per definire un metodo che può prendere in input una lista di frutti o di qualsiasi sua sottoclasse.
In questo caso, il metodo non può aggiungere un elemento alla lista, ma può leggere gli elementi presenti nella lista.
Da sapere
Estendere Classi Generiche
È possibile estendere classi generiche o implementare interfacce generiche con classi a loro volta generiche, è possibile scegliere il tipo nelle loro sotto classi.
public class ClasseBase`<T>` { private T valore; public ClasseBase(T valore) { this.valore = valore; } } public class ClasseEstesa`<K, T>` extends ClasseBase`<T>` { private K chiave; public ClasseEstesa(K chiave, T valore) { super(valore); this.chiave = chiave; }}
Vincoli sui parametri di tipo generico
Puoi aggiungere vincoli sui parametri di tipo generico per specificare quali tipi di dati sono ammessi estendendo il tipo generico.
`public class Collezione<T extends Numero> { // codice della classe }`
In questo esempio, il parametro di tipo generico T è vincolato a estendere la classe Numero. Ciò significa che la classe Collezione può lavorare solo con oggetti che estendono la classe Numer
Utilizzo di più parametri di tipo generico
Puoi utilizzare più parametri di tipo generico per creare classi più complesse. Ciò ti consente di definire più di un tipo di dati che la classe può gestire.
`public class Mappa<K, V> { // codice della classe }`
In questo esempio, la classe Mappa ha due parametri di tipo generico K e V. La classe può lavorare con qualsiasi tipo di chiave (K) e valore (V). Ad esempio, potresti utilizzare la classe Mappa per creare una mappa di stringhe e interi, o una mappa di oggetti personalizzati e valori di tipo Boolean.
Metodi generici in classi non generiche
È possibile definire metodi generici in classi non generiche.
Esempio:
public class Esempio { public `<T>` void stampa(T elemento) { System.out.println(elemento); } }
Overloading Metodi Generici
Un metodo generico può essere sovraccaricato anche da un metodo non generico ma con lo stesso nome e numero di parametri. Quando il compilatore riceve una chiamata a questo metodo cerca prima l’implementazione più specifica, ovvero quella non generica e poi il generico, per garantire un’ottimizzazione in base al tipo.
static public<T extends Comparable<T>> T getMassimo(T a, T b, T c){ ... }static public String getMassimo(String a, String b, String c){ ... }