Strutture dati "classiche"

Dimensione: px
Iniziare la visualizzazioe della pagina:

Download "Strutture dati "classiche""

Transcript

1 Modulo di Roadmap 0. Primi passi con Java 1. Buone abitudini 2. Tipi di dati primitivi 3. Uso di classi 4. Leggere e scrivere 5. Definire metodi 6. Strutture di controllo 7. Array e Collection 8. Progetto di classi 9. Ereditarietà 10. Eccezioni 11. Stream 12. Ricorsione 13. Strutture dati + Generics Strutture dati "classiche" Tipi di dati astratti, di uso generale, per i quali sono definiti alcuni metodi standard Data la loro struttura generale e la loro utilità universale, possono venire realizzate in qualunque linguaggio di programmazione Spesso hanno natura ricorsiva Esamineremo dapprima le strutture fornite dalla libreria Java, quindi vedemo come scriverne noi Strutture dati + Generics 1

2 Modulo di Java Collection Framework A partire da Java 2, il package java.util contiene il Java Collection Framework (JCF) un insieme di interface, classi e classi astratte progettate organicamente per facilitare la creazione e l utilizzo di vari tipi di strutture dati collettive Alcune classi esistenti nelle versioni precedenti (p.e. Vector) ) sono state riadattate in modo da far parte del JCF Un framework complesso Il JCF ha subito costanti evoluzioni (e ingrandimenti) in tutte le versioni di Java seguite alla 1.2 Nella versione 6 esso comprende: 19 interface (5 di supporto), 6 classi astratte, 12 classi istanziabili di uso generale, 2 classi contenenti solo metodi static di utilità, 16 classi per usi speciali, 2 classi di eccezioni Vedremo alcune nozioni generali, per un analisi dettagliata è più produttivo consultare la documentazione Strutture dati + Generics 2

3 Modulo di Roadmap 13. Strutture dati + Generics Tipi di dati parametrici Tipi di dati parametrici (da Java 1.5 in poi) Come già visto implicitamente per i Vector,, le definizioni di interface e classi nel JCF sono parametriche rispetto ad altri tipi di dati Ad esempio, la nozione di Vector è quella di un contenitore di qualunque tipo di dato al suo interno, pertanto essa è parametrica rispetto al tipo di dato contenuto Sintatticamente questo è espresso dalle parentesi angolari <> che racchiudono il tipo di dato parametrico Strutture dati + Generics 3

4 Modulo di Tipi di dati parametrici public class Vector<E> La documentazione riporta la definizione che è parametrica rispetto ad un tipo di dato generico E... Vector <String> > elenco = new Vector<String String>(); Quando si utilizza concretamente la classe, il tipo generico viene sostituito con un tipo specifico (in questo caso String) Roadmap 13. Strutture dati + Generics Tipi di dati parametrici Le interface Collection, Iterable, Iterator Strutture dati + Generics 4

5 Modulo di L interface Collection public interface Collection<E> extends Iterable E l interface basilare del JCF e rappresenta una raccolta generica di oggetti di tipo E E implementata dalla maggior parte delle classi (anche astratte) del JCF Specifica 15 metodi di cui 6 opzionali Uno dei metodi più significativi è iterator(), ereditato a sua volta dall interface Iterable L interface Collection Sorvoliamo su alcuni dettagli da approfondire in seguito boolean add(e e) Ensures that this collection contains the specified element (optional operation). boolean addall(collection Collection<? extends E> c) Adds all of the elements in the specified collection to this collection (optional operation). void clear() Removes all of the elements from this collection (optional operation). boolean contains(object o) Returns true if this collection contains the specified element. boolean containsall(collection Collection<?> c) Returns true if this collection contains all of the elements in the specified collection. boolean equals(object o) Compares the specified object with this collection for equality. Strutture dati + Generics 5

6 Modulo di L interface Collection Sorvoliamo su alcuni dettagli da approfondire in seguito int hashcode() Returns the hash code value for this collection. boolean isempty() Returns true if this collection contains no elements. Iterator<E> iterator() Returns an iterator over the elements in this collection. boolean remove(object o) Removes a single instance of the specified element from this collection, if it is present (optional operation). boolean removeall(collection Collection<?> c) Removes all of this collection's elements that are also contained in the specified collection (optional operation). boolean retainall(collection Collection<?> c) Retains only the elements in this collection that are contained in the specified collection (optional operation). L interface Collection Sorvoliamo su alcuni dettagli da approfondire in seguito int size() Returns the number of elements in this collection. Object[] toarray() Returns an array containing all of the elements in this collection. <T> T[] toarray(t[] a) Returns an array containing all of the elements in this collection; ; the runtime type of the returned array is that of the specified array. Strutture dati + Generics 6

7 Modulo di L interface Iterable public interface Iterable<T> L interface Iterable specifica un solo metodo: Iterator<T> iterator() Il metodo iterator() restituisce un riferimento a un Iterator<T> ovvero a un iteratore sul tipo di oggetti contenuti nella Collection Iterator è a sua volta un interface L interface Iterator public interface Iterator<E> L interface Iterator specifica i metodi che deve fornire un iteratore iteratore ovvero un oggetto capace di scorrere uno a uno gli elementi di una raccolta boolean hasnext() Returns true if the iteration has more elements. E next() Returns the next element in the iteration. void remove() Removes from the underlying collection the last element returned by the iterator (optional operation). Strutture dati + Generics 7

8 Modulo di Uso di iteratori Gli iteratori offrono un modo alternativo (e tecnicamente più efficiente) di eseguire un operazione su tutti gli elementi di una Collection Vector <String> > elenco = new Vector<String String>();... Iterator<String String> > lancetta = elenco.iterator iterator(); while (lancetta.hasnext hasnext()) System.out.println println(lancetta. (lancetta.next()); Roadmap 13. Strutture dati + Generics Tipi di dati parametrici Le interface Collection, Iterable, Iterator Famiglie di Collection: : Set, List, Queue, Deque Strutture dati + Generics 8

9 Modulo di Famiglie di Collection La documentazione di JCF indica 3 principali famiglie di Collection,, corrispondenti alle seguenti interface che ereditano da Collection: Set<E> List <E> Queue<E> e Deque <E> L interface Set Un Set è una Collection con il vincolo aggiuntivo di non contenere elementi duplicati (come gli insiemi della teoria degli insiemi) Di fatto non aggiunge la specifica di nuovi metodi a quelli ereditati da Collection ma aumenta i requisiti di alcuni metodi (p.e. add) in modo che sia garantita la non duplicazione Strutture dati + Generics 9

10 Modulo di Le interface SortedSet L interface SortedSet estende Set con il requisito che esista una relazione di ordine totale tra gli elementi (siano tutti confrontabili) Questo rende possibile la specifica di metodi aggiuntivi: la selezione del primo o ultimo elemento nell ordine la selezione di tutti gli elementi prima o dopo un certo elemento la selezione degli elementi in un certo range Le interface NavigableSet L interface NavigableSet estende SortedSet con svariati metodi di utilità aggiuntiva tra i quali: la selezione dell elemento più vicino (prima o dopo nell ordine, con o senza uguaglianza) rispetto a un elemento specificato la possibilità di enumerare il Set in ordine inverso... Strutture dati + Generics 10

11 Modulo di L interface List Una List è una Collection nella quale gli elementi sono ordinati posizionalmente e sono disponibili operazioni (accesso, inserimento, eliminazione, ricerca, estrazione, sostituzione) riferite ad indici posizionali Oltre all Iterator modello base è possibile creare un ListIterator che permette movimenti bidirezionali sulla List ed operazioni di modifica del contenuo Operazioni posizionali su List Inserimento void add(int index,, E element) Inserts the specified element at the specified position in this list (optional operation). boolean addall(int index, Collection<? extends E> c) Inserts all of the elements in the specified collection into this list at the specified position (optional operation). E get(int index) Returns the element at the specified position in this list. Accesso int indexof(object Object o) Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element. int lastindexof(object Object o) Returns the index of the last occurrence of the specified element in this list, or -1 if this list does not contain the element. Ricerca: si cerca un elemento tale che o.equals(elemento) sia true Strutture dati + Generics 11

12 Modulo di Operazioni posizionali su List Eliminazione E remove(int index) Removes the element at the specified position in this list (optional operation). E set(int index,, E element) Replaces the element at the specified position in this list with the specified element (optional operation). Sostituzione List<E> sublist(int int fromindex, int toindex) Returns a view of the portion of this list between the specified fromindex,, inclusive, and toindex, exclusive. Estrazione di una sottolista tra due posizioni (la prima inclusa, la seconda no) L interface ListIterator Si può creare un ListIterator in due modi (a partire dall inizio della lista o da una posizione specificata) ListIterator<E> listiterator() Returns a list iterator over the elements in this list (in proper sequence). ListIterator<E> listiterator(int int index) Returns a list iterator of the elements in this list (in proper sequence), starting at the specified position in this list. Strutture dati + Generics 12

13 Modulo di L interface ListIterator Oltre ai due metodi per scorrere avanti ed al remove disponibili in Iterator,, offre: due metodi duali per scorrere indietro due metodi per sapere in che posizione è l iteratore aggiunta e sostituzione di un elemento L interface ListIterator boolean hasprevious() Returns true if this list iterator has more elements when traversing the list in the reverse direction. E previous() Returns the previous element in the list. Scorrimento all indietro int nextindex() Returns the index of the element that would be returned by a subsequent call to next. int previousindex() Returns the index of the element that would be returned by a subsequent call to previous. Posizione dell iteratore Strutture dati + Generics 13

14 Modulo di L interface ListIterator void add(e e) Inserts the specified element into the list (optional operation). L operazione di aggiunta è riferita alla posizione dell iteratore void set(e e) Replaces the last element returned by next or previous with the specified element (optional operation). void remove() Removes from the list the last element that was returned by next or previous (optional operation). Le operazioni set e remove sono riferite all ultimo elemento acceduto con next o previous (se non esiste o è stato già eliminato, si ha un eccezione) Prima di add size()-1 previous next Strutture dati + Generics 14

15 Modulo di Dopo add previous next L interface Queue Rappresenta una coda ovvero una struttura dove si inseriscono elementi (ingresso in coda) e solo un elemento (la testa della coda) è disponibile per l accesso o per l estrazione (uscita dalla coda) La politica di selezione dell elemento in testa è tipicamente FIFO ma sono possibili anche code con scavalcamento basato su qualche criterio di ordinamento o priorità Strutture dati + Generics 15

16 Modulo di L interface Queue Esistono tre operazioni base su una Queue: inserimento di un elemento in coda accesso all elemento in testa (senza toglierlo dalla coda) estrazione dell elemento in testa (eliminandolo dalla coda) Di ognuna esistono due versioni a seconda del comportamento quando l operazione non è possibile: una lancia un eccezione, l altra ritorna false o null L interface Queue Lanciano eccezione Non lanciano eccezione boolean add(e e) Aggiunta elemento E element() Accesso elemento in testa E remove() Estrazione elemento in testa boolean offer(e e) Aggiunta elemento E peek() Accesso elemento in testa E poll() Estrazione elemento in testa Strutture dati + Generics 16

17 Modulo di L interface Deque Il nome Deque sta per double ended queue è un interface che estende Queue poiché rappresenta una struttura con due estremi (testa e coda, head e tail) ) ed operazioni di inserimento, accesso ed estrazione su entrambi gli estremi Come nel caso precedente, per ogni operazione esistono due versioni (una lancia eccezioni, una no) I metodi ereditati da Queue sono affiancati da metodi equivalenti con nomi più specifici Deque: : operazioni in testa Lanciano eccezione Non lanciano eccezione boolean addfirst(e e) Aggiunta elemento in testa E getfirst () Accesso elemento in testa E removefirst () Estrazione elemento in testa boolean offerfirst (E e) Aggiunta elemento in testa E peekfirst () Accesso elemento in testa E pollfirst () Estrazione elemento in testa Strutture dati + Generics 17

18 Modulo di Deque: : operazioni in coda Lanciano eccezione Non lanciano eccezione boolean addlast(e e) Aggiunta elemento in coda E getlast () Accesso elemento in coda E removelast () Estrazione elemento in coda boolean offerlast (E e) Aggiunta elemento in coda E peeklast () Accesso elemento in coda E polllast () Estrazione elemento in coda Deque: : metodi aggiuntivi Iterator<E> descendingiterator() Returns an iterator over the elements in this deque in reverse sequential order. boolean removefirstoccurrence(object Object o) Removes the first occurrence of the specified element from this deque. boolean removelastoccurrence(object Object o) Removes the last occurrence of the specified element from this deque. Strutture dati + Generics 18

19 Modulo di Roadmap 13. Strutture dati + Generics Tipi di dati parametrici Le interface Collection, Iterable, Iterator Famiglie di Collection: : Set, List, Queue, Deque La nozione di Stack La nozione di stack (pila) Operazioni solo sulla cima della pila: push (aggiunta), pop (estrazione), peek (accesso) Strutture dati + Generics 19

20 Modulo di La nozione di stack Uno stack è una forma limitata di Deque nella quale si opera sempre in testa e mai in coda L interface Deque specifica tre metodi con i nomi tipici della nozione di stack (essi affiancano tre metodi equivalenti della specifica generale) Deque: : metodi per uso a stack Nomi metodi stack Metodi equivalenti void push(e e) Aggiunta elemento in cima E peek () Accesso elemento in cima E pop () Estrazione elemento in cima boolean addfirst(e e) Aggiunta elemento in testa E peekfirst () Accesso elemento in testa E removefirst () Estrazione elemento in testa Strutture dati + Generics 20

21 Modulo di Ripensamenti Il package java.util contiene (da sempre) anche una classe Stack,, derivata da Vector, con i tre metodi push, pop e peek Il suo uso è tuttavia sconsigliato e si suggerisce di usare un implementazione di Deque nel modo sopra indicato La classe Stack non fa parte del JCF Roadmap 13. Strutture dati + Generics Tipi di dati parametrici Le interface Collection, Iterable, Iterator Famiglie di Collection: : Set, List, Queue, Deque La nozione di Stack L interface Map Strutture dati + Generics 21

22 Modulo di L interface Map Il JCF non si basa solo sull interface Collection ma anche sull interface parallela Map<K,V> Mentre la Collection è una raccolta semplice di elementi, la Map è una raccolta indicizzata di elementi: ogni elemento viene inserito accompagnato da un altro elemento detto chiave (key)) che lo identifica univocamente Questo permette di accedere agli elementi di una Map non solo tramite iterazione o accesso posizionale, ma anche tramite accesso diretto basato su chiave Uso di chiavi L uso di chiavi univoche è molto diffuso per l identificazione di elementi: codice fiscale per le persone matricola per studenti universitari targhe per autoveicoli codice a tre lettere per aeroporti (e alfanumerico per ciascun volo) codici di prenotazione Strutture dati + Generics 22

23 Modulo di L interface Map L interfaccia Map<K, V> è parametrica rispetto a due tipi di dati: K rappresenta il tipo usato come chiave V rappresenta il tipo degli elementi contenuti nella Map e indicizzati dalle chiavi L interface Map: : relazioni con Collection Anche se non è formalmente derivata dall interface Collection,, l interface Map ha alcune parentele con essa: alcuni metodi, (clear( clear, isempty, size) ) con lo stesso nome e lo stesso ruolo che hanno in Collection tre possibilità di accedere al contenuto di una Map sotto forma di Collection Strutture dati + Generics 23

24 Modulo di L interface Map: : aggiunta con metodo put V put(k key,, V value) Se la key passata come argomento non è presente nella map, viene aggiunta la nuova coppia key-value Se invece la key è già presente, il nuovo value sostituisce quello precedentemente associato alla key L interface Map: : metodo put put("vrn", new Airport("Verona",...)) FCO MXP BGY Roma Fiumicino Milano Malpensa Bergamo Strutture dati + Generics 24

25 Modulo di L interface Map: : metodo put put("vrn", new Airport("Verona",...)) VRN FCO MXP BGY Verona Roma Fiumicino Milano Malpensa Bergamo L interface Map: : metodo put put("bgy", new Airport("Milano Orio",...)) VRN FCO MXP BGY Verona Roma Fiumicino Milano Malpensa Bergamo Strutture dati + Generics 25

26 Modulo di L interface Map: : metodo put put("bgy", new Airport("Milano Orio",...)) VRN FCO MXP BGY Verona Roma Fiumicino Milano Malpensa Milano Orio L interface Map: : accesso (con o senza eliminazione) V remove (Object key) Se la key passata come argomento è presente nella map, viene restituito un riferimento al value corrispondente e la coppia keyvalue viene eliminata dalla map, altrimenti viene restituito null V get(object key) Se la key passata come argomento è presente nella map, viene restituito un riferimento al value corrispondente, altrimenti viene restituito null. Il contenuto della Map rimane immutato. Strutture dati + Generics 26

27 Modulo di L interface Map: verifiche di presenza boolean containskey(object key) Returns true if this map contains a mapping for the specified key. boolean containsvalue(object value) Returns true if this map maps one or more keys to the specified value. L interface Map: vista come Collection Sono specificati metodi per: ottenere una Collection contenente tutti i values presenti nella Map Collection<V> values() Returns a Collection view of the values contained in this map. ottenere un Set contenente tutte le keys presenti nella Map Set<K> keyset() Returns a Set view of the keys contained in this map. Per definizione non ci possono essere chiavi duplicate Strutture dati + Generics 27

28 Modulo di L interface Map: vista come Collection ottenere un Set contenente tutte le coppie key- value presenti nella Map Set<Map Map.Entry<K,V>> entryset() Returns a Set view of the mappings contained in this map. Poiché non ci possono essere chiavi duplicate nemmeno le coppie possono essere duplicate Gli elementi del Set implementano un apposita interface dedicata alla rappresentazione di coppie key-value Le interface SortedMap e NavigableMap In modo analogo a quanto visto per Set, l interface SortedMap estende Map aggiungendo un requisito di ordinamento totale sulle chiavi (e relativi metodi di utilità) L interface NavigableMap estende SortedMap con altri metodi di utilità (concettualmente analoghi a quelli di NavigableSet) Strutture dati + Generics 28

29 Modulo di Roadmap 13. Strutture dati + Generics Tipi di dati parametrici Le interface Collection, Iterable, Iterator Famiglie di Collection: : Set, List, Queue, Deque La nozione di Stack L interface Map Dalle interface alle classi Le classi ArrayList e Vector La classi ArrayList e Vector forniscono entrambe un implementazione dell interface List La classe ArrayList fornisce in aggiunta 3 metodi di natura accessoria La classe Vector ha svariati metodi ulteriori che riflettono il suo progetto precedente (metodi affiancati con nomi diversi ma funzionalità identiche, operazioni sulla struttura interna di uso piuttosto raro, ) Strutture dati + Generics 29

30 Modulo di Le classi ArrayList e Vector La classe ArrayList è leggermente più efficiente di Vector,, in compenso ArrayList non è predisposta per la programmazione multi-thread thread (argomento trattato in corsi successivi) mentre Vector sì Negli usi più comuni ArrayList e Vector possono essere considerate all incirca equivalenti Alcuni libri di testo suggeriscono di usare ArrayList anziché Vector in quanto ArrayList è stata progettata nel contesto di JCF, ma non ci sono forti motivi per preferire l una o l altra a livello didattico iniziale La classe ArrayDeque La classe ArrayDeque offre un implementazione dell interface Deque senza alcun metodo aggiuntivo Non è predisposta per la programmazione multi-thread thread Strutture dati + Generics 30

31 Modulo di La classe LinkedList La classe LinkedList ha una natura multiforme in quanto implementa sia l interface List sia l interface Deque (può quindi essere usata in alternativa ad entrambe) Non è predisposta per la programmazione multi-thread thread Le classi HashSet e LinkedHashSet La classe HashSet fornisce una semplice implementazione dell interface Set (il nome della classe deriva dalla struttura dati interna utilizzata per l implementazione e non visibile) La classe LinkedHashSet è figlia di HashSet e aggiunge la caratteristica di mantenere sempre lo stesso ordine quando si effettua l enumerazione degli elementi (non garantito da HashSet) Strutture dati + Generics 31

32 Modulo di La classe TreeSet La classe TreeSet fornisce un implementazione dell interface NavigableSet (il nome della classe deriva dalla struttura dati interna utilizzata per l implementazione e non visibile) Le tre classi HashSet, LinkedHashSet e TreeSet non sono predisposte per la programmazione multi-thread thread Le classi HashMap e Hashtable Entrambe le classi HashMap e Hashtable forniscono un implementazione dell interface Map La classe Hashtable ha alcuni metodi aggiuntivi che riflettono il suo progetto preesistente, inoltre non ammette elementi o chiavi null (mentre HashMap sì) ed è predisposta per la programmazione multi-thread thread (mentre HashMap no) Il rapporto tra HashMap e Hashtable è simile a quello tra ArrayList e Vector Strutture dati + Generics 32

33 Modulo di La classe LinkedHashMap La classe LinkedHashMap è figlia di HashMap e aggiunge la caratteristica di mantenere sempre lo stesso ordine quando si effettua l enumerazione degli elementi (non garantito da HashMap) Il rapporto tra LinkedHashMap ed HashMap è del tutto analogo a quello tra LinkedHashSet ed HashSet La classe TreeMap La classe TreeMap fornisce un implementazione dell interface NavigableMap (il nome della classe deriva dalla struttura dati interna utilizzata per l implementazione e non visibile) Le tre classi HashMap, LinkedHashMap e TreeMap non sono predisposte per la programmazione multi-thread thread Strutture dati + Generics 33

34 Modulo di Classi solo static : Arrays La classe Arrays contiene 105 metodi (molti in overloading) ) per svolgere comuni operazioni utili su array (di tipi semplici o di Object) ) tra le quali: aslist: : restituzione di una List con il contenuto dell array binarysearch (18 versioni): ricerca efficiente di un elemento (richiede che l array sia ordinato) copyof (10 versioni): crea una copia dell array in un nuovo array copyofrange (10 versioni): crea una copia di un range dell array in un nuovo array equals (9 versioni): confronta il contenuto di due array fill (18 versioni): riempimento con un certo valore sort (18 versioni): ordinamento tostring (9 versioni) Classi solo static : Collections La classe Collections contiene 52 metodi per svolgere svariate operazioni (anche non molto comuni) su Collection tra le quali: binarysearch (2 versioni) copy fill max e min (2 versioni ciascuno) replaceall reverse rotate shuffle (2 versioni) sort (2 versioni) swap Strutture dati + Generics 34

35 Modulo di Roadmap 13. Strutture dati + Generics Tipi di dati parametrici Le interface Collection, Iterable, Iterator Famiglie di Collection: : Set, List, Queue, Deque La nozione di Stack L interface Map Dalle interface alle classi Guardando dentro: classi e interface generiche Classi e interface generiche Le interface e le classi appartenenti al JCF sono definite genericamente rispetto al tipo di oggetto contenuto in ciascuna specifica istanza di Collection Tale tipo generico è specificato tra <> e viene detto parametro di tipo o variabile di tipo La definizione di classi generiche (ossia con uno o più parametri di tipo) è una delle principali innovazioni introdotte nella versione 1.5 Essa è particolarmente adatta alle strutture dati ma può essere utilizzata ovunque lo si ritenga utile Strutture dati + Generics 35

36 Modulo di Confronto di stringhe descrittive Si supponga di voler confrontare le stringhe descrittive di oggetti dello stesso tipo in questo modo: se hanno una parte iniziale uguale (non vuota) questa viene restituita in uscita in caso contrario viene restituito null Prima della 1.5 Poiché il metodo tostring è definito nella classe Object e il tipo Object è compatibile con qualunque altro tipo, si potrebbe definire un metodo di confronto che riceve in ingresso due Object String confrontadescrizioni(object obj1,object obj2)... così però potrei finire per confrontare le pere con le mele (oggetti di tipo diverso) Strutture dati + Generics 36

37 Modulo di La classe Object come jolly Prima della versione 1.5 la classe Object veniva spesso usata come jolly per indicare qualunque classe Ad esempio tutte le strutture dati del JCF erano definiti come contenitori di Object,, cosa che permetteva di mescolare oggetti diversi nella stessa struttura ma obbligava a fare il cast esplicito per accedere agli elementi contenuti secondo il loro vero tipo Due requisiti in conflitto? Nella maggior parte dei casi non si desiderano strutture con contenuto eterogeneo, anzi la presenza di elementi eterogenei è indice di un errore e potrebbe causare eccezioni al momento del cast Tuttavia si desidera la definizione di strutture dati e metodi generici, cioè in grado di operare su qualunque classe (altrimenti dovremmo definire una nuova classe contenitore per ogni nuova classe di oggetto contenuto) Strutture dati + Generics 37

38 Modulo di Due requisiti in conflitto? Genericità: deve essere possibile definire classi generiche con capacità universali o comunque riferite ad un ampio insieme di classi Type safety: : nell ambito di classi generiche si vogliono evitare mescolanze indesiderate di tipi. Eventuali errori sui tipi devono essere rilevati al momento della compilazione anziché generare eccezioni in esecuzione. Due requisiti in conflitto? Nelle versioni precedenti alla 1.5 i due requisiti erano effettivamente in conflitto: per ottenere la genericità si usava Object come jolly, rinunciando così alla type safety A partire dalla versione 1.5 si possono soddisfare entrambi i requisiti usando il meccanismo delle classi generiche Strutture dati + Generics 38

39 Modulo di Una classe generica La definizione della classe è parametrica rispetto ad un tipo T public class ComparatoreDescrizioni <T> private T obj1; La classe ha due private T obj2; attributi del tipo T public ComparatoreDescrizioni(T _obj1, T _obj2) obj1 = _obj1; obj2 = _obj2; Il costruttore riceve due argomenti formali di tipo T public void iniziocomune() // DETTAGLI RIPORTATI IN SEGUITO MA NON RILEVANTI Una classe generica public String iniziocomune() String descr1 = obj1.tostring tostring(); String descr2 = obj2.tostring tostring(); int i = 0; StringBuffer parteuguale = new StringBuffer(); while (i < descr1.length length() && i < descr2.length length() && descr1.charat charat(i) == descr2.charat charat(i)) parteuguale.append append(descr1. (descr1.charat(i)); i++; String inizio = parteuguale.tostring tostring(); if (inizio.length length() == 0) return null; else return inizio; Strutture dati + Generics 39

40 Modulo di Un main che la utilizza public class MainComparatoreString public static void main (String[] args) String s1 = MyUtil.leggiString leggistring("inserire il primo termine"); String s2 = MyUtil.leggiString leggistring("inserire il secondo termine"); ComparatoreDescrizioni<String String> > prova = new ComparatoreDescrizioni<String String>(s1,s2); String risultato= prova.iniziocomune iniziocomune(); System.out.println println(risultato); Il tipo generico T viene sostituito da String in questo utilizzo Se s1 ed s2 non fossero di tipo String si avrebbe errore in compilazione Un altro main che la utilizza public class MainComparatoreDatiPersonali... public static void main (String [] args) DatiPersonali dati1 = creadati(); DatiPersonali dati2 = creadati(); ComparatoreDescrizioni <DatiPersonali> > prova = new ComparatoreDescrizioni<DatiPersonali DatiPersonali> > (dati1,dati2); String risultato = prova.iniziocomune iniziocomune(); Il tipo generico T viene sostituito da DatiPersonali in questo utilizzo Strutture dati + Generics 40

41 Modulo di Classi generiche: regole e convenzioni La definizione di una classe generica è parametrica rispetto ad uno o più parametri di tipo, indicati tra <> dopo il nome della classe Per convenzione i parametri di tipo vengono indicati con singole lettere maiuscole: T, S, U per qualunque uso E per indicare elementi di strutture collettive K,V per indicare chiave e valore di Map Classi generiche: regole e convenzioni I nomi dei parametri di tipo possono essere usati (quasi) come qualunque altro nome di tipo nella definizione di una classe ma ci sono delle limitazioni: non è possibile creare istanze di tipi parametrici (un istruzione new T non è possibile) non è possibile definire attributi static di un tipo parametrico o usare un tipo parametrico all interno di metodi static Strutture dati + Generics 41

42 Modulo di Classi generiche: regole e convenzioni Al momento dell uso ciascun parametro di tipo generico deve essere sostituito con un tipo strutturato I tipi elementari non sono utilizzabili a questo scopo Metodi generici In qualunque classe (generica o no) è possibile definire metodi generici, la cui definizione dipende da uno o più variabili di tipo (riferite al solo metodo, non alla classe di cui fa parte) Le variabili di tipo sono specificate tra <> prima del tipo ritornato Esse devono comparire anche nella definizione del tipo di argomenti formali Strutture dati + Generics 42

43 Modulo di Metodi generici Se un metodo generico è definito in una classe generica non bisogna far confusione tra variabili di tipo del solo metodo e variabili di tipo dell intera classe Spesso i metodi generici sono static e definiti in classi non generiche Esempi di metodi generici static sono presenti nelle classi (non generiche) Arrays e Collections Un esempio di metodo generico (classe Arrays) Il metodo copyof è generico rispetto al tipo T Il metodo restituisce un array di T Il primo argomento formale del metodo è a sua volta un array di T static <T> T[] copyof(t[] original, int newlength) Copies the specified array, truncating or padding with nulls (if necessary) ) so the copy has the specified length. Strutture dati + Generics 43

44 Modulo di Metodi generici L invocazione di un metodo generico non richiede di specificare esplicitamente la sostituzione tra parametri di tipo e tipi effettivamente utilizzati: il compilatore riconosce la sostituzione dal passaggio dei parametri (purchè( compatibile con la definizione) Anche per i metodi generici non è possibile utilizzare tipi elementari come sostituti di parametri di tipo Vincoli sui parametri di tipo In alcuni casi non si vuole che una classe generica sia universale (cioè che ciascun parametro di tipo sia sostituibile da qualunque tipo strutturato): il tipo generico può essere vincolato a rispettare delle caratteristiche I vincoli specificabili sono legati alle relazioni di ereditarietà Strutture dati + Generics 44

45 Modulo di Vincolo extends Si può imporre al tipo generico di essere derivato da una certa classe o interface In questo modo qualunque tipo sostituito avrà delle caratteristiche comuni garantite sulle quali la definizione della classe generica può fare affidamento Il vincolo viene espresso tra le <> usando la parola chiave extends p.e. <T extends Comparable> Vincolo super In modo analogo è possibile imporre il vincolo di essere superclasse di un tipo specificato Il vincolo è espresso usando la parola chiave super tra <> Strutture dati + Generics 45

46 Modulo di Il carattere jolly (wildcard( wildcard) Il carattere? all interno di <> rappresenta l indicazione jolly di qualunque tipo Esso è utilizzabile in combinazione con i vincoli extends o super o da solo Se usato da solo esso indica una sostituzione totalmente libera (che però potrebbe essere vincolata indirettamente da altre parti della definizione della classe o del metodo) Esempi di vincoli e jolly Il metodo copy (classe Collections) è generico rispetto al tipo T Esso riceve come argomenti una List sorgente src da cui copiare gli elementi e una List destinazione dest dove metterli. Poiché List è un interface generica va specificato il tipo contenuto di entrambe static <T> void copy(list<? super T> dest, List<? extends T> src) Copies all of the elements from one list into another. La List destinazione deve contenere elementi di una superclasse di T La List sorgente deve contenere elementi di una sottoclasse di T Strutture dati + Generics 46

47 Modulo di Esempi di vincoli e jolly Il metodo addall è definito nell interface Collection che è parametrica rispetto al tipo E dei suoi elementi (qui richiamato) Esso riceve come argomento una Collection c da cui pescare gli elementi da aggiungere boolean addall(collection Collection<? extends E> c) Adds all of the elements in the specified collection to this collection (optional operation). La Collection c deve contenere elementi di una sottoclasse di E Esempi di vincoli e jolly Il metodo containsall è definito nell interface Collection che è parametrica rispetto al tipo E dei suoi elementi (qui non richiamato) Esso riceve come argomento una Collection c da cui pescare gli elementi di cui verificare la presenza boolean containsall(collection Collection<?> c) Returns true if this collection contains all of the elements in the specified collection. La Collection c può contenere elementi di qualunque classe Strutture dati + Generics 47

48 Modulo di Roadmap 13. Strutture dati + Generics Tipi di dati parametrici Le interface Collection, Iterable, Iterator Famiglie di Collection: : Set, List, Queue, Deque La nozione di Stack L interface Map Dalle interface alle classi Guardando dentro: classi e interface generiche Esempi di programmazione di strutture dati classiche : liste concatenate e alberi binari Liste concatenate Si parla di lista concatenata per indicare una struttura nella quale gli elementi (detti anche nodi) sono collegati tramite riferimenti dall uno all altro Il caso più semplice di lista concatenata prevede un collegamento unidirezionale sequenziale a partire da un elemento iniziale Strutture dati + Generics 48

49 Modulo di Lista concatenata semplice Il primo elemento viene detto testa della lista ed è riferito da una variabile che non è un elemento della lista Questo riferimento è necessario per accedere alla lista Ogni elemento, tranne l ultimo, riferisce l elemento successivo Lista concatenata doppia Ogni elemento contiene due riferimenti (uno all elemento successivo, uno al precedente) Il primo elemento contiene solo il riferimento al successivo, l ultimo elemento solo al precedente Strutture dati + Generics 49

50 Modulo di Lista circolare semplice L ultimo elemento è collegato al primo in modo da chiudere circolarmente il collegamento. Le nozioni di primo e ultimo elemento non sono più essenziali. Rimane la necessità di un riferimento esterno dal quale partire Lista circolare doppia I riferimenti tra gli elementi realizzano un doppio anello Strutture dati + Generics 50

51 Modulo di Doppio riferimento In tutte le varianti, oltre che il riferimento alla testa si può mantenere il riferimento anche all ultimo elemento (coda) della lista Strutture dati ricorsive Gli elementi di strutture dati concatenate sono tipicamente definiti in modo ricorsivo: : infatti ogni elemento deve contenere un riferimento ad un elemento dello stesso tipo class NodoLista private NodoLista prossimo;... class NodoListaDoppia private NodoListaDoppia prossimo; private NodoListaDoppia precedente;... Strutture dati + Generics 51

52 Modulo di Generics e classi interne Per rappresentare una struttura dati sono tipicamente necessarie almeno due classi: una che rappresenta la struttura (p.e. la lista) nel suo complesso e una per rappresentare i singoli elementi della lista Per definire strutture parametriche rispetto ai dati contenuti negli elementi entrambe le classi devono essere generiche (p.e. Lista<E>, ElementoLista<E>) Si vuole però specificare che il tipo di una lista e dei suoi elementi deve essere lo stesso. Questo non è possibile se le due classi sono definite separatamente: bisogna invece definire una classe internamente all altra (inner( class) Classi interne E possibile definire una classe all interno di un altra La classe interna non può essere public e tipicamente sarà private (il suo uso è limitato alla classe che la contiene) L uso di classi interne (che non verrà approfondito) è utile in situazioni molto specifiche Una di queste è la condivisione di una variabile di tipo: la classe interna sarà definita in termini della variabile di tipo della classe esterna Strutture dati + Generics 52

53 Modulo di Classi Lista e NodoLista public class Lista<E> private NodoLista private NodoLista testa; primo elemento della lista private int numeroelementi;...// METODI VARI DI Lista da approfondire dopo La classe e i suoi attributi private class NodoLista sono private, ma visibili all interno della classe Lista private E contenuto; private NodoLista prossimo;...// METODI DI NodoLista da approfondire dopo La classe Lista è parametrica rispetto ad un tipo E In caso di lista semplice è sufficiente il solo attributo testa che riferisce il La classe NodoLista è interna a Lista. Essa non è esplicitamente generica ma la sua definizione può contenere la variabile di tipo E della classe esterna. La classe NodoLista private class NodoLista private E contenuto; private NodoLista prossimo; public NodoLista (E _contenuto) contenuto = _contenuto; prossimo = null; La classe NodoLista ha due semplici costruttori: - uno per quando non c è un elemento successivo - uno per quando c è public NodoLista (E _contenuto, NodoLista _prossimo) contenuto = _contenuto; prossimo = _prossimo; Strutture dati + Generics 53

54 Modulo di La classe NodoLista private class NodoLista... public String tostring() return contenuto.tostring tostring(); La stringa descrittiva di un nodo coincide con la stringa descrittiva del suo contenuto La classe Lista: costruttori public class Lista<E> private NodoLista testa; private int numeroelementi; public Lista() testa = null; numeroelementi=0; La classe Lista ha due semplici costruttori: - uno creare una lista vuota, priva di elementi - uno per quando il primo elemento è specificato public Lista(E primoelemento) testa = new NodoLista(primoElemento primoelemento); numeroelementi=1;... Strutture dati + Generics 54

55 Modulo di public class Lista<E>... public boolean isempty() return testa == null; La classe Lista: metodi di utilità La lista è vuota se il primo riferimento è null public String tostring() if (isempty()) All interno del metodo tostring si ha il tipico return MESS_VUOTA; modo di scorrere tutti gli elementi di una lista semplice else StringBuffer risultato = new StringBuffer(); NodoLista corrente = testa; Viene fissato un riferimento all inizio della lista while (corrente!= null) La scansione termina quando il riferimento diventa null risultato.append append(corrente. (corrente.tostring() + "\n");" corrente = corrente.prossimo; L avanzata avviene passando al prossimo elemento return risultato.tostring tostring(); Scansione di una lista testa corrente null contenuto contenuto contenuto contenuto contenuto next next next next next=null Strutture dati + Generics 55

56 Modulo di Inserimento in testa public void inserimentointesta(e contenuto) Il numero di elementi cresce e il nuovo numeroelementi++; NodoLista sarà riferito da testa in ogni caso if (isempty()) testa = new NodoLista(contenuto); Se la lista è vuota, il nuovo nodo è anche l unico e avrà null come successore else testa = new NodoLista(contenuto, testa); Altrimenti avrà come successore quello che prima era in testa Inserimento in coda public void inserimentoincoda(e contenuto) Se la lista è vuota, numeroelementi++; testa e coda coincidono if (isempty()) testa = new NodoLista(contenuto); else Si porta il riferimento corrente all ultimo elemento NodoLista corrente = testa; while(corrente.prossimo!= null) corrente=corrente.prossimo; corrente.prossimo=new NodoLista(contenuto);... e si aggiunge lì il nuovo nodo Strutture dati + Generics 56

57 Modulo di Inserimento in una posizione public void inserimento(int int posizione, E contenuto) throws ArrayIndexOutOfBoundsException if (posizione < 0 posizione > numeroelementi) throw new ArrayIndexOutOfBoundsException(); Controllo che la posizione sia valida if (posizione == 0) inserimentointesta(contenuto); else Sappiamo già gestire le if (posizione == numeroelementi) inserimentoincoda(contenuto); posizioni estreme else Si porta il riferimento corrente NodoLista corrente = testa; for (int i=1; i < posizione; i++) all elemento in posizione corrente=corrente.prossimo; precedente a quello nuovo corrente.prossimo=new NodoLista(contenuto,corrente.prossimo); numeroelementi++; e si aggiunge lì il nuovo nodo Rimozione in testa public E rimozioneintesta() throws NoSuchElementException Controllo che ci sia almeno un elemento da togliere if (isempty()) throw new NoSuchElementException(); E risultato = testa.contenuto; Metto via il risultato testa=testa.prossimo; Spostando avanti il riferimento ottengo un eliminazione implicita numeroelementi-- --; return risultato; Strutture dati + Generics 57

58 Modulo di Rimozione in coda public E rimozioneincoda() ()throws NoSuchElementException Controllo che ci sia almeno un elemento da togliere if (isempty()) throw new NoSuchElementException(); Se l elemento è uno solo so già come fare if (numeroelementi == 1) return rimozioneintesta(); else Porto il riferimento corrente sul penultimo elemento NodoLista corrente = testa; for (int i=1; i < numeroelementi-1; i++) corrente=corrente.prossimo; E risultato = corrente.prossimo.contenuto; corrente.prossimo = null; numeroelementi-- --; return risultato; Fondamenti di Programmazione Metto via il risultato Mettendo a null il riferimento ottengo un eliminazione implicita Rimozione in una posizione public E rimozione (int( posizione) throws ArrayIndexOutOfBoundsException if (posizione < 0 posizione > numeroelementi-1) throw new ArrayIndexOutOfBoundsException(); if (posizione == 0) return rimozioneintesta(); else if (posizione == numeroelementi-1) return rimozioneincoda(); else NodoLista corrente = testa; for (int i=1; i < posizione; i++) corrente=corrente.prossimo; E risultato = corrente.prossimo.contenuto; corrente.prossimo=corrente.prossimo.prossimo; numeroelementi-- --; return risultato; Fondamenti di Programmazione Controllo che la posizione sia valida Sappiamo già gestire le posizioni estreme Si porta il riferimento corrente all elemento in posizione precedente a quello da eliminare L eliminazione si ottiene con uno scavalcamento Strutture dati + Generics 58

59 Modulo di Alberi binari Un albero binario è un albero con la caratteristica che ogni nodo può avere al più due figli Gli alberi binari (in forme più sofisticate di quella elementare che vedremo noi) sono molto utilizzati per la gestione di dati dotati di ordinamento, in quanto consentono l effettuazione efficiente di operazioni di inserimento e ricerca Albero binario Strutture dati + Generics 59

60 Modulo di Albero binario CA B E GOR PU AB C G O PU R A B G P R U B P U Le classi BTree e TreeNode La classe BTree è generica rispetto ad un tipo E che deve estendere l interface Comparable (deve essere possibile stabilire tra due elementi quale viene prima) public class BTree <E extends Comparable<? super E>> Poiché l interface Comparable è a sua volta generica, si specifica il relativo tipo. In pratica si indica private TreeNode root; che E può estendere Comparable direttamente o anche ereditandola da una superclasse... private class TreeNode private E value; private TreeNode left; private TreeNode right;... Analogamente al caso precedente, la classe BTree ha un solo attributo per riferire la radice dell albero e contiene come classe interna quella relativa ai nodi Strutture dati + Generics 60

61 Modulo di La classe BTree public class BTree <E extends Comparable<? super E>> private TreeNode root; public BTree() root = null; L albero viene costruito inizialmente vuoto public void insertnode(e newelement) Per l inserimento di un nodo: if ( root == null ) - se l albero è vuoto si crea root = new TreeNode(newElement newelement); semplicemente la radice else - altrimenti si invoca sulla root.insert insert(newelement); radice un metodo di inserimento della classe TreeNode La classe TreeNode private class TreeNode private E value; private TreeNode left; private TreeNode right; public TreeNode (E _value_ value) value = _value_ value; left = null; right = null; public String tostring() return value.tostring tostring(); Strutture dati + Generics 61

62 Modulo di Metodo di inserimento "ordinato" in TreeNode public void insert(e newelement) Il nuovo elemento è if (newelement.compareto(value)) < 0) minore del valore di questo nodo: si va a sinistra if (left == null) Se a sinistra non c è nulla, left = new TreeNode(newElement newelement); si crea lì un nuovo nodo else Altrimenti si invoca ricorsivamente left.insert insert(newelement); il metodo sul figlio di sinistra else if (newelement.compareto(value)) > 0) Il caso di elemento maggiore è del tutto if (right == null) analogo right = new TreeNode(newElement newelement); else right.insert insert( newelement ); Se newelement è un doppione, viene ignorato Ricerca di un elemento (nella classe BTree) public boolean binarysearch(e tofind) Il metodo di ricerca return searchhelper(root root, tofind); pubblico si avvale di un metodo ricorsivo privato private boolean searchhelper(treenode node,, E wanted) Se nella ricerca si arriva a null, if (node == null) ) return false; l elemento cercato non è presente if (wanted.compareto(node.value)) == 0) return true; Qui abbiamo trovato l elemento if (wanted.compareto(node.value)) < 0) return searchhelper(node node.left, wanted); else return searchhelper(node node.right, wanted); La ricerca, se non è terminata, prosegue ricorsivamente a sinistra o a destra Strutture dati + Generics 62

63 Modulo di Traversata in ordine public void inordertraversal() inorderhelper(root root); private void inorderhelper(treenode node) if (node == null) return; inorderhelper(node node.left); System.out.println println(node.tostring()); //o qualunque altra operazione inorderhelper(node node.right); Traversata in preordine public void preordertraversal() preorderhelper(root root); private void preorderhelper(treenode node) if (node == null) return; System.out.println println(node.tostring()); //o qualunque altra operazione preorderhelper(node node.left); preorderhelper(node node.right); Strutture dati + Generics 63

64 Modulo di Traversata in postordine public void postordertraversal() postorderhelper(root root); private void postorderhelper(treenode node) if (node == null) return; postorderhelper(node node.left); postorderhelper(node node.right); System.out.println println(node.tostring()); //o qualunque altra operazione Strutture dati + Generics 64