2. Le collezioni di oggetti Java mette a disposizione classi per contenere oggetti Liste,Vettori HashTable Set Sino a Java 1.4 queste classi lavoravano solo con Object, a partire da Java 1.5 e' stato introdotta una nuova sintassi
3. Object e le collezioni Object è la superclasse implicita di qualunque oggetto Object è un tipo generico , dal momento che qualunque classe è un Object Grazie a questa proprietà di Object, è possibile creare oggetti di dimensione variabile come Vector
4. Difetti delle collezioni di Object Utilizzando queste classi ci si rende subito conto che si ha la necessità di ricorrere al casting per recuperare gli oggetti presenti nel contenitore non esiste nessun controllo sul tipo di oggetti che effettivamente vengono inseriti nel Vector; se per errore si inserisce in v un oggetto diverso da String, la cosa verrà notata soltanto in fase di esecuzione Vector v = new Vector(); v.add( "Polly" ); // necessario il cast String s = (String)v.get(0); Vector v = new Vector(); v.add( "Polly" ); // ClassCastException Long l = (Long)v.get(0);
5. Generics Polimorfismo Parametrico Per risolvere i problemi citati in Java 1.5 è stato introdotto il concetto di Generics I Generics hanno una nuova sintassi Il tipo della collezione si dichiara in fase di definizione dell'oggetto Errori di assegnazioni a oggetti non corretti vengono segnalati in fase di compilazione anziché in fase di esecuzione
6. Generics Un esempio Vector<String> v = new Vector<String>(); v.add( "Polly" ); // Nessun cast necessario String s = v.get(0); // da errore di compilazione Long l = v.get (0); Vector v = new Vector(); v.add( "Polly" ); // necessario il cast String s = (String)v.get(0); Senza Generics Con Generics
7. Sintassi /1 Definizione La nuova sintassi prevede la forma Vector<E> La E tra parentesi angolari denota un Tipo Parametrico deciso dall'utilizzatore Grazie alla nuova sintassi il controllo sui tipi viene effettuato dal compilatore Vector<String> v = new Vector<String>(); v.add( "Polly" ); v.add(new Integer(10)) ; // da errore di compilazione String s = v.get(0); // Nessun cast necessario Long l = v.get (0); // da errore di compilazione
8. Sintassi /2 Metodi La classe Vector ha dei metodi che devono lavorare con i generics ad esempio add() I metodi vengono dichiarati utilizzando il meta tipo boolean add(E o) // prende in ingresso un generic E get( int index) // restituisce un generic Il simbolo E verrà sostituito automaticamente con il tipo dichiarato in fase di creazione Vector<String> v = new Vector<String>(); v.add( "Polly" );
9. Generics Non solo Vector Vector è solo un esempio, i generics possono essere usati ad esempio sugli oggetti java.util.Map HashMap lavora con la coppia chiave, valore che vengono gestiti entrambi come generics HashMap<K,V> HashMap<Integer, String> map = new HashMap<Integer, String>(); map.put(10, "dieci" ); map.put(20, "venti" ); String s = map.get(10);
10. Generics e valori di ritorno Gli Identificatori generics possono restituire come valore di ritorno una classe parametrica dello stesso tipo di quello dichiarato al costruttore. Il "vecchio" iterator() public Iterator iterator() diventa public Iterator<E> iterator() Questo permette di avere iterators che non necessitano di casting
11. Esempi di iterators Iterator i = v.iterator(); while (i.hasNext()) { String s = (String)i.next(); System. out .println(s); } Iterator<String> i = v.iterator(); while (i.hasNext()) { String s = i.next(); System. out .println(s); } Senza Generics Con Generics
12. Generics definiti dall'utente I Generics non sono soltato utilizzate dal JDK Possono essere usati per creare proprie classi Vediamo in dettaglio come funziona
13. Classe parametrica Linea 1: La classe viene dichiarata con due tipi generics Gli attributi ed i metodi fanno riferimento al meta tipo public class Coppia<K, V> { private K k ; private V v ; public Coppia(K k, V v) { this . k = k; this . v = v; } public K getKey(){ return k ; } public V getValue(){ return v ; } }
14. Utilizzo classe Coppia Cane c = new Cane( "Fido" ); Coppia<String, Cane> nome1; nome1 = new Coppia<String, Cane>(c.getNome(), c); Anatra a = new Anatra( "Paperino" ); Coppia<String, Anatra> nome2; nome2 = new Coppia<String, Anatra>(a.getNome(), a); La classe Coppia rappresenta un oggetto con tue valori Vengono creati due oggetti
15. Peculiarità dei generics /1 Tutte le istanze di una classe generica condividono la stessa classe di base, anche se vengono specializzate in modo differente. Coppia<String, Cane> nome1 = new Coppia<String, Cane>(...); Coppia<String, Anatra> nome2 new Coppia<String, Anatra>(...); Ci si aspetta che le due classi siano diverse per tipo ma esse condividono la stessa classe base quindi il confronto nome1.getClass() == nome2.getClass() ritorna true
16. Peculiarità dei generics /2 Non è possibile assegnare un'istanza di una classe parametrica ad un reference con tipo parametrico più generico Vector<String> v1 = new Vector<String>(); Vector<Object> v2 = v1; // errore di compilazione Domanda 1: Perchè questo non è consentito? Domanda 2: E' un difetto oppure un vantaggio?
17. Type safety sicurezza nell'assegnamento dei tipi Se una simile operazione fosse consentita, si otterrebbero due reference di tipo parametrico diverso che puntano allo stesso Vector di String. Potremmo inserire all'interno di tale vettore oggetti differenti da String usando il reference v2: v2.add(new Object); // violazione della type safety
18. Collezioni di tipo sconosciuto Abbiamo visto che se si desidera definire una Collection in grado di accettare qualunque tipo , non possiamo usare Vector<Object> Essa denota una Collection in grado di accettare unicamente oggetti di tipo Object Per risolvere quello problema è possibile definire generics di tipo parametrico sconosciuto Questo si fa utilizzando il carattere '?' (punto interrogativo)
19. Utilizzo /1 Il codice riportato di seguito crea due vettori che contengono rispettivamente solo Cani e solo Anatre (ricordiamo che entrambi gli oggetti estendono la classe Animale) chiama un metodo in grado di stampare oggetti di tipo Animale Sappiamo, per quanto detto prima, che Vector<Animale> accetta solo Animale e non sue classi derivate Vector<Cane> cani = new Vector<Cane>(); Vector<Anatra> anatre = new Vector<Anatra>(); t.stampaAnimali(cani); // errore di compilazione t.stampaAnimali(anatre); // errore di compilazione public void stampaAnimali(Vector<Animale> c) { ... }
20. Utilizzo /2 Upper bound Il metodo stampaAnimali deve essere reso capace di gestire la gerarchia Animale Si utilizza la tecnica upper bound che permette di ricevere elementi di tipo Animale e di tutte le sue sottoclassi La sintassi utilizza il wildcard e la parola extends public void stampaAnimali(Vector<? extends Animale> c) { ... }
21. Utlizzo /3 Lower bound Molto meno usato del precedente, il lower bound permette di ricevere solo oggetti di un certo tipo o di una sua superclasse La sintassi utilizza il wildcard e la parola super public void stampaCani(Vector<? super Cane> c) { ... }