Generic - Giuseppe · 1 Generic Contenitori generici per oggetti realizzati tramite il concetto di...
Transcript of Generic - Giuseppe · 1 Generic Contenitori generici per oggetti realizzati tramite il concetto di...
1
Generic
Contenitori generici per oggetti realizzati
tramite il concetto di tipo parametrico
IL NUOVO APPROCCIO (java 1.5)È sbagliato abolire il controllo di tipo!
Occorre un altro modo per esprimere genericità, che consenta un controllo di tipo a compile time
type safety: "se si compila, è certamente corretto"Java 1.5 introduce i tipi parametrici ("generici")
Il tipo può essere un parametro:in funzioni statiche, in classi e metodi di classi notazione <TIPO>Si possono definire relazioni fra "tipi generici“recupera il "lato buono" dell'ereditarietà fra collezioni, inquadrandolo in un contesto solido.
2
Generics
Un generic è un metodo che è ricompilato con differenti tipi secondo le necessità (simile ai template C++)Svantaggi:
Invece di : List words = new ArrayList();Si deve definire:
ArrayLIst<String> words = new ArrayList<String>();
Vantaggi:Fornisce una migliore gestione del type checking durante la compilazioneEvita il casting da Object. I.e., invece di
String title = ((String) words.get(i)).toUppercase();utilizzaremo
String title = words.get(i).toUppercase();
Esempio di classe parametricaclass tipo <T> {
T attributo;public tipo (T x){attributo = x;}public T getValue() {return attributo;}}
3
Uso della classe parametricapublic class Prova {
public static void main(String []s){tipo <String> p1 = new tipo<String>(s[0]);tipo <Integer> p2 = new tipo<Integer>(10);String a = p1.getValue();System.out.println(a);Integer b = p2.getValue();System.out.println(b);
}}
Tutta la JFC è stata riscritta per far uso dei generici
Anche classi preesistenti (come Vector) sono state reingegnerizzate e riscritte in accordo al nuovo idiomaLe operazioni sulla JFC "generica" si dicono checked (controllate) o type-safe (sicure come tipo)
4
public interface List<E> { void add(E x);Iterator<E> iterator();
}
public interface Iterator<E> { E next();boolean hasNext();
}
List<Integer> myIntList = new LinkedList<Integer>(); // 1’myIntList.add(new Integer(0)); //2’Integer x = myIntList.iterator().next(); // 3’
List myIntList = new LinkedList(); // 1myIntList.add(new Integer(0)); // 2Integer x = (Integer) myIntList.iterator().next(); // 3
Tipi parametrizzati Tutte le occorrenze del parametro “formal type” (E in questo caso) sono sostituite dagli argomenti i “actual type” (in questo caso Integer).
5
List<String> ls = new ArrayList<String>(); //1List<Object> lo = ls; //2
L’istruzione 2 genera un errore in compilazione
In generale, se C2 è una classe derivata da C1 e G è una dichiarazione generica, non è vero che G<C2> è un tipo derivato da G<C1>.
Tipi generici ed Ereditarietà
Verso iI concetto di"tipo parametrico variante"
6
Abbiamo visto che, se B deriva da A, NON si può dire che una collezione di elementi di B derivi dalla collezione di elementi di A, perché in generale ciò non ha senso (operazioni impossibili)
ALCUNE operazioni potrebbero anche essere sicure (negli array, la lettura), ma ciò non è vero in generale.
CLASSI GENERICHE ed EREDITARIETÀ
Consideriamo la classe generica LinkedList <T>:prendiamo due sue "istanziazioni"
LinkedList <Number> LinkedList<Integer>
Sono due tipi diversi, incompatibili fra loro!Si evita così il rischio di situazioni "apparentemente giuste ma in realtà sbagliate" stile array.Per verificarlo, immaginiamo di creare due liste:
LinkedList<Number> l1 = new LinkedList<Number>();LinkedList<Integer> l2 = new LinkedList<Integer>();
e consideriamo i due possibili assegnamenti:l1 = l2 //errorel2 = l1 //errore
7
Tipi parametrici variantiCovarianza, controvarianza,
bivarianza
Dai TIPI GENERICI a TIPI PARAMETRICIVARIANTI ("WILDCARD")
L'esperimento precedente ha mostrato che non ha senso cercare una compatibilitàgenerale fra tipi parametrici, perché non può esistere.Ha senso invece cercare compatibilità fra casi specifici e precisamente fra tipi di parametri di singoli metodi.Perciò, alla normale notazione dei tipi generici List<T>, usata per creare oggetti, si affianca una nuova notazione, pensata esplicitamente per esprimere i tipi accettabili come parametri in singoli metodiSi parla quindi di tipi parametrici varianti, in Java più brevemente detti WILDCARD.
8
WILDCARD, ovvero TIPI COVARIANTI,CONTROVARIANTI, BIVARIANTI
Alla luce di ciò:la notazione List<T> denota il normale tipo genericoil tipo covariante List<? extends T>
fattorizza le proprietà dei List<X> in cui X estende Tsi usa per specificare tipi che consentono solo "letture"
il tipo controvariante List<? super T>fattorizza le proprietà dei List<X> in cui X è esteso da Tsi usa per specificare tipi che consentono solo "scritture"
il tipo bivariante List<?>fattorizza tutti i List<T> senza distinzionesi usa per specificare tipi che non consentono né letture né scritture (ma possono servire comunque…)
Wildcardsvoid printCollection(Collection c) {
Iterator i = c.iterator();for (k = 0; k < c.size(); k++) {
System.out.println(i.next());}}void printCollection(Collection<Object> c) { for (Object e : c) { System.out.println(e);}}
void printCollection(Collection<?> c) { for (Object e : c) { System.out.println(e);}}
Wildcards
9
Esempiopublic class MyList<T> {
private T head;private MyList<T> tail;public T getHead(){ return head; }public <E extends T> void setHead(E element){
head=element; }}
MyList<Number> list1 = new MyList<Number>();MyList<Integer> list2 = new MyList<Integer>();list1.setHead( new Double(1.4) ); // OK!list1.setHead( list2.getHead() ); // OK!
Esempiopublic class MyList<T> {
private T head;private MyList<T> tail;public T getHead(){ return head; }public <E extends T> void setHead(E element){
head=element; }public void setTail(MyList<T> l){ tail=l; }public MyList<? extends T> getTail(){ return tail; }
}
MyList<? extends Number> list3 = list1.getTail();MyList<? extends Number> list4 = list2.getTail();MyList<? extends Integer> list5 = list2.getTail();I primi due restituiscono una lista di Number, compatibile col tipo "lista di qualcosa che estenda Number“Il terzo restituisce una lista di Integer, compatibile col tipo "lista di qualcosa che estenda Integer"
Restituisce una lista di elementi di tipo T o più specifico di T
10
public class MyList<T> {private T head;private MyList<? extends T> tail;public T getHead(){ return head; }public <E extends T> void setHead(E element){...}public MyList<? extends T> getTail(){ return tail; }public void setTail(MyList<? extends T> l){ tail=l;}
}Non c'è realmente bisogno che la coda sia una lista di T!Possiamo essere più generici! Conseguentemente, possiamo rilassare il vincolo su setTail, il cui argomento ora può essere una lista di qualunque cosa estenda Tlist1.setTail(list2); // SI', ORA VA BENE!
Si rendono così SELETTIVAMENTE possibili TUTTE e SOLE le operazioni "sensate" e significative!
Riflessioni a posterioriNell'esempio precedente abbiamo usato:
il tipo generico MyList<T> per creare oggetti
MyList<Number>, MyList<Integer>, …tipi covarianti come MyList<? extends Number>
fattorizza le proprietà dei tipi di liste che estendono Number, come MyList<Integer>, MyList<Double>, o MyList<Number> stessa
NON abbiamo invece usato: tipi controvarianti come MyList<? super Number>
fattorizzerebbe le proprietà di tutte le liste di tipi più generici di Number, come ad esempio MyList<Object>
il tipo bivariante MyList<?>
11
public abstract class Shape { public abstract void draw(Canvas c);}
public class Circle extends Shape { private int x, y, radius;public void draw(Canvas c) { ... }
}
public class Rectangle extends Shape { private int x, y, width, height;public void draw(Canvas c) { ... }
}
public void drawAll(List<Shape> shapes) { for (Shape s: shapes) { s.draw(this);}}
public void drawAll(List<? extends Shape> shapes) { ... }
List<? extends Shape> è un esempio di bounded wildcard. Il simbolo? Sta per un tipo sconosciutoSappiamo che in questo caso tale tipo sconosciuto e un subtype di Shape. Diremo che Shape è un upper bound di una wildcard.
12
Generic Methods
Supponiamo di voler scrivere un metodo che prende un array di objects è una collection e pone gli oggetti dell’array in una collection.Soluzione
static void fromArrayToCollection(Object[] a, Collection<?> c) { for (Object o : a) { c.add(o); //
compile time error}}
I metodi generici consentono di superare un tale problema.Cosi come in una dichiarazione di tipo, la dichiarazione di un metodo può essere generica cioè parametrizzata rispeotto ad uno o più parametri
static <T> void fromArrayToCollection(T[] a, Collection<T> c)
{ for (T o : a) { c.add(o); // correct}}
13
Enhanced for loopInvece di
void cancelAll(Collection c) { for (Iterator i = c.iterator(); i.hasNext(); ) {
TimerTask tt = (TimerTask) i.next();tt.cancel();
}}
Si può utilizzare:void cancelAll(Collection<TimerTask> c) {
for (TimerTask task : c)task.cancel();
}
void cancelAll(Collection<TimerTask> c) {foreach TimerTask task of c) //C# notation
task.cancel();}
Le strutture di java.util
14
ListeNel package java.util
due tipi di listeentrambe implementano l’interfaccia java.util.List
ArrayList: basata su array e indicatore di riempimentoLinkedList:basata su rappresentazione collegata
Attenzione - i due tipi di liste hanno prestazioni diverse nelle operazioni fondamentali
public class LinkedList<E> extendsAbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable
public class ArrayDeque<E> extends AbstractCollection<E>implements Deque<E>, Cloneable, Serializable
public class Vector<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, Serializable
15
la rappresentazione con array facilita l’accesso agli elementi data la posizione ma penalizza gli inserimenti e le cancellazioni in mezzo alla lista è necessario spostare gli elementi su o giù
ViceversaLinkedList è più lenta nell’accesso agli elementi data la posizione
accedere l’elemento in posizione i richiede la scansione di i riferimentima è più veloce negli inserimenti e nelle cancellazioni (approssimativamente costano quanto la scansione)
Un test di prestazioniBasato su circa 1000 operazionitempi misurati in millisecondi
16
Considerazioni sulle prestazionigli array sono i più veloci, ma non consentono inserimenti e cancellazioniArrayList è veloce nell’accesso agli elementi, lenta in inserimenti e cancellazioni in mezzoLinkedList è più lenta nell’accesso, ma decisamente piùveloce in inserimenti e canc.Vector è più lenta di entrambe e non dovrebbe essere utilizzata
Di conseguenzanel caso di liste a bassa dinamica, per ridurre i tempi di scansione è opportuno usare ArrayListper liste ad alta dinamica, con frequenti inserimenti e cancellazioni conviene utilizzare LinkedListE che succede se devo cambiare tipo ? es: passare da ArrayList a LinkedList
Linea guidaè opportuno programmare con le interfacce invece che con le implementazionile interfacce riducono l’accoppiamento tra leclassi e semplificano i cambiamenti
Nel caso delle listeè opportuno utilizzarle per quanto possibile attraverso riferimenti di tipo java.util.List
In questo modole modifiche sono semplificatebasta cambiare le poche istruzioni in cui gli oggetti di tipo lista sono creati cambiando la classe usata per l’implementazioneil resto dell’applicazione resta intattai metodi si comportano polimorficamente e viene utilizzata la nuova implementazione
17
IteratoriAttenzione
in questo approccio, quando manipolo la lista devo tenere in considerazione che l’implementazione potrebbe cambiare
In particolaredevo fare attenzione a non basare la scrittura del codice su una o l’altra delle implementazioniUn’operazione critica: la scansione
Il modo tipico di scandire una lista utilizzandoindici interi
for (int i = 0; i < lista.size(); i++) {Object o = lista.get(i);// operazioni su o}
IteratoriQuesto tipo di scansione
è particolarmente adatto ad ArrayList (il metodo get viene eseguito rapidamente)ma disastrosamente lenta su LinkedList
Perchè ?perchè come detto l’accesso all’elemento in posizione i di una LinkedList richiede di scandire i elementi (i operazioni circa)
Detta n la dimensione della lista 1 + 2 + 3 + 4 + ... + npari circa a n(n +1)/2, ovvero dell’ordine di n2
es: per una lista di 100 elementi: 5000nel caso di ArrayList: circa 100 operazioni
In casi normaliil problema non sorge (liste piccole)ma in alcuni casi si tratta di un costo di calcolo che può diventare inaccettabile
18
Il problemala scansione attraverso indici interi NON è la scansione più
naturale per LinkedListArrayList
implementazione basata su indici scansione naturale basata su indici
LinkedListimplementazione basata su riferimenti
scansione naturale basata su riferimentiIdealmente
vorrei che per ciascuna tipologia di lista potesse essere utilizzata automaticamente la scansione più adattasenza che il programmatore se ne debba preoccupare
Attenzionein questo caso il polimorfismo da solo non basta
La scansione della lista è un’operazione che deve necessariamente essere effettuata da un oggetto diverso dalla lista, non posso quindi semplicemente sovrascrivere il metodo “scandisciti()” e utilizzarlo polimorficamente devo necessariamente definire altri oggetti la cui responsabilità èquella di scandire la lista
Soluzioneutilizzare un oggetto “iteratore”
Iteratoreoggetto specializzato nella scansione di una listafornisce al programmare un’interfaccia per effettuare la scansione in modo indipendente dalla strategia di scansione concreta (indici, puntatori, ecc.) implementa la scansione in modo ottimale per ciascun tipo di lista
19
L’utilizzo in java.utilinterfaccia java.util.Iterator, che prevede i seguenti metodi
Object next() per spostarsi in avantiboolean hasNext() per fermarsi
esiste poi una implementazione per ArrayListed una implementazione per LinkedList
Iteratore per ArrayListutilizza indici interi
Iteratore per LinkedListscandisce la lista usando i riferimenti
Come si ottiene l’iteratore ?utilizzando il metodo Iterator iterator() di java.util.List
Dettagli sugli iteratori di java.utilsostanzialmente si basano sui metodi next() e previous() forniti dalle due listesono però più complicati di quanto si pensa dal momento che consentono anche di modificare la lista durante la scansioneattraverso il metodo remove()senza doversi preoccupare della consistenzadei riferimenti
20
Una novità di J2SE 1.5il ciclo for migliorato (“enhanced for loop”)un modo sintatticamente compatto per utilizzare un iteratore su una collezione
Sintassifor (<Tipo> <riferimento> : <Collezione>)
{<operazioni su <riferimento>>}
Ricerca e rimozione di un elementoE’ necessario che la classe degli elementi cotenuti nella lista implementi il metodo equals
21
MappeUna mappa, ovvero un dizionario associativo
classe java.util.HashMapimplementa l’interfaccia java.util.Map
Dizionario associativocollezione in cui i riferimenti sono salvati con un “nome”, detto chiavetipicamente una stringapossono successivamente essere recuperati rapidamente utilizzando la chiave
22
public interface Map<K,V>Principali metodi
V put(K key, V value)void putAll(Map<? extends K,? extends V> m)V get(Object key)V remove(Object key)
Implementazioni java.util.HashMapjava.util.TreeMapjava.util.Hashtable
metodi principali di java.util.Mapvoid put(Object chiave, Object riferimento)Object get(Object chiave)Object remove(Object chiave)int size()
Le principali implementazionijava.util.HashMapjava.util.TreeMapjava.util.Hashtable (versione legacy)
23
Differenze rispetto alle listein una mappa non sono significative le posizioni degli elementi ma le chiavile ricerche sulla base della chiave sono enormemente facilitate (nella lista richiederebbero una scansione)utilizzata tipicamente quando più che le scansioni sono importanti le ricerche
Attenzione peròad ogni chiave può essere associato un unico oggettoput successive con la stessa chiave sostituiscono i valori precedentinon può essere usata quando possono esserci più valori per la stessa chiave
HashMapUn requisito fondamentale per le mappela rapidità di inserimento e cancellazione
L’implementazione fondamentaleHashMap
di gran lunga la più veloce
La tecnica sottostantetecnica di hashing ovvero basata su “funzioni di hashing”
24
HashMapFunzione di hash
funzione che trasforma un valore (“chiave”) di lunghezza variabile in uno di lunghezza fissa (“hash”della chiave)
Caratteristica tipica di una funzione hashl’hash deve essere calcolabile rapidamente
Classificazione delle funzioni hashfunzioni con collisioni o prive di collisioni
Funzione priva di collisionenon ci sono due valori per cui l’hash è ugualepossibile solo se i valori sono finiti
Funzione con collisionepiù valori possono avere lo stesso valore di hashcaso tipico
HashMapImplementazione di put() nella HashMap
la mappa mantiene gli oggetti in un array di N riferimenti a liste (dette “bucket”)ogni elemento della lista memorizza una coppia <chiave, riferimento>viene calcolato il valore della funzione di hash sulla chiave e poi viene applicato un operatore modulo per ridurlo ad un numero tra 0 e N – 1in questo modo si ottiene un indice nell’array; la coppia <chiave, riferimento> viene aggiunta in coda al bucket della posizione ottenuta
25
HashMapImplementazione di get() in HashMap
viene calcolato il valore di hash della chiave per risalire al bucket (indice nell’array) viene scandito il bucket e la chiave viene confrontata con ogni chiavese viene trovata una chiave identica a quella cercata, viene restituito il riferimentoaltrimenti viene restituito null
Due operazioni fondamentaliil calcolo della funzione di hashil confronto tra le chiavi
Calcolo della funzione di hashviene usato il metodo hashCode() ereditato da Object
Confronto tra le chiaviviene utilizzato il metodo equals() ereditato da Object
26
HashMapNota
le implementazioni standard sono basate sull’indirizzo in memoriapotrebbero non essere quelle adatte a fare hashing in alcuni casi
Nelle classi principali della piattaformasono ridefinite opportunamente quando è necessario
Di conseguenzaè opportuno utilizzare come chiave per le mappe oggetti di classi note es: String, Integer, ecc.Nel caso in cui questo non sia possibile
per la classe di oggetti da utilizzare come chiavi ènecessario ridefinire opportunamente hashCode() ed equals()
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, SerializableCapacity: numero di buckets nella hash table, Load factor e’ la misuradi quanto piena puo ‘ essere l’hash table prima di un resize (0,75)Costruttori
HashMap() costruisce una HashMap vuota con capacity =16 e load factor = 0.75
HashMap(int initialCapacity) costruisce una HashMap vuota con capacity = initialCapacity e load factor = 0.75
HashMap(int initialCapacity, float loadFactor) costruisce HashMap con i valori specificati
HashMap(Map<? extends K,? extends V> m) costruisce una HashMap con lo stesso mappings della mappa scecificata.
27
InsiemiAltre classi significative delle collezioni
interface java.util.Set: rappresenta una collezione non ordinata e priva di duplicatidue principali implementazioni: HashSet e TreeSetinterface java.util.Collection: rappresenta una collezione generica (può essere un insieme oppure una lista)tutte le collezioni sono scandibili con iteratori
A cosa servono gli insiemi ?possono essere quasi sempre sostituiti dalle listein alcuni casi sono più naturali
Esempio: Gestione Appuntamentipotrei utilizzare un oggetto di tipo java.util.Set per rappresentare l’insieme dei partecipanti ad una Riunione
InsiemiA questo punto possiamo vedere come scandire una mappadue metodi principali
ottengo l’insieme delle chiavi con Set keySet() e scandisco l’insieme con un iteratore e prelevo dalla mappa tutti gli elementiottengo la collezione dei valori con il metodo Collection values() ottengo un iteratore dalla collezione e scandisco tutti gli elementi
Perchè Set e Collection ?per rispettare la regola secondo cui si programma con le interfacce e non con le implementazioniInfatti
Una HashMap è una collezione non ordinata cioe mantiene un insieme di chiavi e non una lista
28
OrdinamentiIn Java
l’ordinamento delle collezioni è una funzionalità offerta dalla piattaforma es: java.util.Collections.sort(java.util.List list) ma bisogna rispettare delle regole
In particolareper ordinare una collezione di riferimenti, gli oggetti relatividevono essere confrontabilirispetto ad una precisa relazione di ordine
Esempiola lista degli Impegniun ordinamento possibile: per orarioun altro ordinamento: in ordine alfabetico rispetto alla descrizioneun altro ordinamento ancora: rispetto al luogo di svolgimentoper poter ordinare una lista di impegnibisogna decidere il criterio di ordinamento
In Javagli oggetti ordinabili devono implementare l’interfaccia java.lang.Comparable
java.lang.Comparableprevede un unico metodoint compareTo(Object o)risultato > 0 se this segue o nell’ordinamentorisultato < 0 se this precede o nell’ordinam.risultato = 0 se this non precede nè segue o
29
Ordinamenti
Notal’utilizzo di Comparable prevede che sia possibile cambiare il codice delle classi che definiscono gli oggetti da ordinareper implementare Comparable e definire compareTo()se questo non è possibile, è possibile utilizzare un approccio alternativoutilizzare un oggetto esterno comparatore
L’interfaccia java.util.Comparatorunico metodo: int compare(Object o1, Object o2)gli oggetti che implementano l’interfaccia sono quindi capaci di confrontare oggetti secondo algoritmi specifici