Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Roadmap • 0. Primi passi con Java • 7. Array e Collection • 1. Buone abitudini • 8. Progetto di classi • 2. Tipi di dati primitivi • 9. Ereditarietà • 3. Uso di classi • 10. Eccezioni • 4. Leggere e scrivere • 11. Stream • 5. Definire metodi • 12. Ricorsione • 6. Strutture di controllo • 13. Strutture dati + Generics Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 1 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 2 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Roadmap • 13. Strutture dati + Generics ¾ Tipi di dati parametrici Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 3 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Tipi di dati parametrici public class Vector<E> Vector<E> La documentazione riporta la definizione che è parametrica rispetto ad un tipo di dato generico E . . . Vector <String> String> elenco = new Vector< Vector<String>(); String>(); Quando si utilizza concretamente la classe, il tipo generico viene sostituito con un tipo specifico (in questo caso String) Fondamenti di Programmazione Roadmap • 13. Strutture dati + Generics ¾ Tipi di dati parametrici ¾ Le interface Collection, Iterable, Iterator Fondamenti di Programmazione Strutture dati + Generics 4 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola L’interface Collection public interface Collection<E> 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 Fondamenti di Programmazione L’interface Collection Sorvoliamo su alcuni dettagli da approfondire in seguito boolean add(E add(E e) Ensures that this collection contains the specified element (optional operation). operation). boolean addAll( addAll(Collection<? Collection<? extends E> c) Adds all of the elements in the specified collection to this collection (optional operation). operation). void clear() clear() Removes all of the elements from this collection (optional operation). operation). boolean contains( contains(Object o) Returns true if this collection contains the specified element. element. boolean containsAll( containsAll(Collection<?> Collection<?> c) Returns true if this collection contains all of the elements in the specified collection. collection. boolean equals( equals(Object o) Compares the specified object with this collection for equality. equality. Fondamenti di Programmazione Strutture dati + Generics 5 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola L’interface Collection Sorvoliamo su alcuni dettagli da approfondire in seguito int hashCode() hashCode() Returns the hash code value for this collection. collection. boolean isEmpty() isEmpty() Returns true if this collection contains no elements. elements. Iterator<E> Iterator<E> iterator() iterator() Returns an iterator over the elements in this collection. collection. boolean remove( remove(Object o) Removes a single instance of the specified element from this collection, collection, if it is present (optional operation). operation). boolean removeAll( removeAll(Collection<?> Collection<?> c) Removes all of this collection's elements that are also contained in the specified collection (optional operation). operation). boolean retainAll( retainAll(Collection<?> Collection<?> c) Retains only the elements in this collection that are contained in the specified collection (optional operation). operation). Fondamenti di Programmazione L’interface Collection Sorvoliamo su alcuni dettagli da approfondire in seguito int size() size() Returns the number of elements in this collection. collection. Object[] Object[] toArray() toArray() Returns an array containing all of the elements in this collection. collection. <T> T[] toArray(T[] toArray(T[] a) Returns an array containing all of the elements in this collection; collection; the runtime type of the returned array is that of the specified array. array. Fondamenti di Programmazione Strutture dati + Generics 6 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione L’interface Iterator public interface Iterator<E> Iterator<E> • L’interface Iterator specifica i metodi che deve fornire un “iteratore” ovvero un oggetto capace di scorrere uno a uno gli elementi di una raccolta boolean hasNext() hasNext() Returns true if the iteration has more elements. elements. E next() next() Returns the next element in the iteration. iteration. void remove() remove() Removes from the underlying collection the last element returned by the iterator (optional operation). operation). Fondamenti di Programmazione Strutture dati + Generics 7 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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> String> elenco = new Vector< Vector<String>(); String>(); . . . Iterator< (); Iterator<String> String> lancetta = elenco.iterator elenco.iterator(); while (lancetta.hasNext ()) (lancetta.hasNext()) System.out.println (lancetta.next next()); ()); System.out.println(lancetta. Fondamenti di Programmazione Roadmap • 13. Strutture dati + Generics ¾ Tipi di dati parametrici ¾ Le interface Collection, Iterable, Iterator ¾ Famiglie di Collection: Set, List, Queue, Deque Fondamenti di Programmazione Strutture dati + Generics 8 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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> Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 9 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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 – ... Fondamenti di Programmazione Strutture dati + Generics 10 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione Operazioni posizionali su List Inserimento void add( add(int index, index, E element) element) Inserts the specified element at the specified position in this list (optional operation). operation). boolean addAll( addAll(int index, index, Collection<? Collection<? extends E> c) Inserts all of the elements in the specified collection into this list at the specified position (optional operation). operation). E get( get(int index) index) Returns the element at the specified position in this list. Accesso int indexOf( indexOf(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. element. int lastIndexOf( lastIndexOf(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. element. Ricerca: si cerca un elemento tale che o.equals(elemento) sia true Fondamenti di Programmazione Strutture dati + Generics 11 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Operazioni posizionali su List Eliminazione E remove( remove(int index) index) Removes the element at the specified position in this list (optional operation). operation). E set(int set(int index, index, E element) element) Replaces the element at the specified position in this list with the specified element (optional operation). operation). Sostituzione List<E> subList( subList(int fromIndex, fromIndex, int toIndex) toIndex) Returns a view of the portion of this list between the specified fromIndex, fromIndex, inclusive, and toIndex, toIndex, exclusive. exclusive. Estrazione di una sottolista tra due posizioni (la prima inclusa, la seconda no) Fondamenti di Programmazione 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<E> listIterator() listIterator() Returns a list iterator over the elements in this list (in proper sequence). sequence). ListIterator<E> ListIterator<E> listIterator( listIterator(int index) index) Returns a list iterator of the elements in this list (in proper sequence), sequence), starting at the specified position in this list. Fondamenti di Programmazione Strutture dati + Generics 12 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione L’interface ListIterator Scorrimento “all’indietro” boolean hasPrevious() hasPrevious() Returns true if this list iterator has more elements when traversing the list in the reverse direction. E previous() previous() Returns the previous element in the list. int nextIndex() nextIndex() Returns the index of the element that would be returned by a subsequent call to next. next. int previousIndex() previousIndex() Returns the index of the element that would be returned by a subsequent call to previous. previous. Posizione dell’iteratore Fondamenti di Programmazione Strutture dati + Generics 13 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola L’interface ListIterator void add(E add(E e) Inserts the specified element into the list (optional operation). 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). operation). void remove() remove() Removes from the list the last element that was returned by next or previous (optional operation). 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) Fondamenti di Programmazione Prima di add ... 0 1 2 previous next size()-1 Fondamenti di Programmazione Strutture dati + Generics 14 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Dopo add ... 0 1 2 3 previous next Fondamenti di Programmazione 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à Fondamenti di Programmazione Strutture dati + Generics 15 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione L’interface Queue • Lanciano eccezione • Non lanciano eccezione boolean add(E e) Aggiunta elemento boolean offer(E e) Aggiunta elemento E element() Accesso elemento in testa E peek() Accesso elemento in testa E remove() Estrazione elemento in testa E poll() Estrazione elemento in testa Fondamenti di Programmazione Strutture dati + Generics 16 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione Deque: operazioni in testa • Lanciano eccezione • Non lanciano eccezione boolean addFirst(E e) Aggiunta elemento in testa boolean offerFirst (E e) Aggiunta elemento in testa E getFirst () Accesso elemento in testa E peekFirst () Accesso elemento in testa E removeFirst () Estrazione elemento in testa E pollFirst () Estrazione elemento in testa Fondamenti di Programmazione Strutture dati + Generics 17 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Deque: operazioni in coda • Lanciano eccezione • Non lanciano eccezione boolean addLast(E e) Aggiunta elemento in coda boolean offerLast (E e) Aggiunta elemento in coda E getLast () Accesso elemento in coda E peekLast () Accesso elemento in coda E removeLast () Estrazione elemento in coda E pollLast () Estrazione elemento in coda Fondamenti di Programmazione Deque: metodi aggiuntivi Iterator<E> Iterator<E> descendingIterator() descendingIterator() Returns an iterator over the elements in this deque in reverse sequential order. order. boolean removeFirstOccurrence( removeFirstOccurrence(Object o) Removes the first occurrence of the specified element from this deque. deque. boolean removeLastOccurrence( removeLastOccurrence(Object o) Removes the last occurrence of the specified element from this deque. deque. Fondamenti di Programmazione Strutture dati + Generics 18 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione La nozione di stack (pila) Operazioni solo sulla cima della pila: push (aggiunta), pop (estrazione), peek (accesso) Fondamenti di Programmazione Strutture dati + Generics 19 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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) Fondamenti di Programmazione Deque: metodi per uso a stack • Nomi metodi stack • Metodi “equivalenti” void push(E e) Aggiunta elemento in cima boolean addFirst(E e) Aggiunta elemento in testa E peek () Accesso elemento in cima E peekFirst () Accesso elemento in testa E pop () Estrazione elemento in cima E removeFirst () Estrazione elemento in testa Fondamenti di Programmazione Strutture dati + Generics 20 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 21 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 22 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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, 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 Fondamenti di Programmazione Strutture dati + Generics 23 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola L’interface Map: aggiunta con metodo put V put(K put(K key, key, V value) 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 Fondamenti di Programmazione L’interface Map: metodo put put("VRN", put("VRN", new Airport("Verona", Airport("Verona", . . .)) “FCO” Roma Fiumicino … … “MXP” Milano Malpensa … … “BGY” Bergamo … … Fondamenti di Programmazione Strutture dati + Generics 24 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola L’interface Map: metodo put put("VRN", put("VRN", new Airport("Verona", Airport("Verona", . . .)) “VRN” “FCO” Verona … … Roma Fiumicino … … “MXP” Milano Malpensa … … “BGY” Bergamo … … Fondamenti di Programmazione L’interface Map: metodo put put("BGY", put("BGY", new Airport("Milano Airport("Milano Orio", . . .)) “VRN” “FCO” Verona … … Roma Fiumicino … … “MXP” Milano Malpensa … … “BGY” Bergamo … … Fondamenti di Programmazione Strutture dati + Generics 25 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola L’interface Map: metodo put put("BGY", put("BGY", new Airport("Milano Airport("Milano Orio", . . .)) “VRN” “FCO” Verona … … Roma Fiumicino … … “MXP” Milano Malpensa … … “BGY” Milano Orio … … Fondamenti di Programmazione L’interface Map: accesso (con o senza eliminazione) V remove (Object key) 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( get(Object key) 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. Fondamenti di Programmazione Strutture dati + Generics 26 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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. Fondamenti di Programmazione L’interface Map: vista come Collection • Sono specificati metodi per: – ottenere una Collection contenente tutti i values presenti nella Map Collection<V> Collection<V> values() values() Returns a Collection view of the values contained in this map. map. – ottenere un Set contenente tutte le keys presenti nella Map Set<K> keySet() keySet() Returns a Set view of the keys contained in this map. map. Fondamenti di Programmazione Strutture dati + Generics Per definizione non ci possono essere chiavi duplicate 27 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola L’interface Map: vista come Collection – ottenere un Set contenente tutte le coppie keyvalue presenti nella Map Set<Map .Entry<K,V>> entrySet() Set<Map.Entry<K,V>> entrySet() Returns a Set view of the mappings contained in this map. 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 Fondamenti di Programmazione 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) Fondamenti di Programmazione Strutture dati + Generics 28 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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, …) Fondamenti di Programmazione Strutture dati + Generics 29 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Le classi ArrayList e Vector • La classe ArrayList è leggermente più efficiente di Vector, in compenso ArrayList non è predisposta per la programmazione multi-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 Fondamenti di Programmazione La classe ArrayDeque • La classe ArrayDeque offre un’implementazione dell’interface Deque senza alcun metodo aggiuntivo • Non è predisposta per la programmazione multi-thread Fondamenti di Programmazione Strutture dati + Generics 30 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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) Fondamenti di Programmazione Strutture dati + Generics 31 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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 (mentre HashMap no) • Il rapporto tra HashMap e Hashtable è simile a quello tra ArrayList e Vector Fondamenti di Programmazione Strutture dati + Generics 32 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 33 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Classi “solo static”: Arrays • La classe Arrays contiene 105 metodi (molti in overloading) overloading) per svolgere comuni operazioni utili su array (di tipi semplici o di Object) Object) tra le quali: – asList: 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) Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 34 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 35 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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( confrontaDescrizioni(Object obj1,Object obj1,Object obj2) • . . . così però potrei finire per confrontare le pere con le mele (oggetti di tipo diverso) Fondamenti di Programmazione Strutture dati + Generics 36 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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) Fondamenti di Programmazione Strutture dati + Generics 37 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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. Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 38 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 { obj1 obj2 } ComparatoreDescrizioni(T ComparatoreDescrizioni(T _obj1, T _obj2) = _obj1; = _obj2; Il costruttore riceve due argomenti formali di tipo T public void inizioComune() inizioComune() {// DETTAGLI RIPORTATI IN SEGUITO MA NON RILEVANTI } } Fondamenti di Programmazione Una classe generica public String inizioComune() inizioComune() { String descr1 = obj1.toString (); obj1.toString(); String descr2 = obj2.toString (); obj2.toString(); int i = 0; StringBuffer parteUguale = new StringBuffer(); StringBuffer(); while (i < descr1.length () && i < descr2.length () && descr1.length() descr2.length() descr1.charAt (i) == descr2.charAt (i)) descr1.charAt(i) descr2.charAt(i)) { parteUguale. charAt(i)); (i)); parteUguale.append(descr1. append(descr1.charAt i++; } String inizio = parteUguale. parteUguale.toString(); toString(); if (inizio.length () == 0) (inizio.length() return null; null; else return inizio; } Fondamenti di Programmazione Strutture dati + Generics 39 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Un main che la utilizza public class MainComparatoreString { public static void main (String[] String[] args) args) { String s1 = MyUtil. MyUtil.leggiString("Inserire leggiString("Inserire il primo termine"); String s2 = MyUtil. MyUtil.leggiString("Inserire leggiString("Inserire il secondo termine"); ComparatoreDescrizioni< ComparatoreDescrizioni<String> String> prova = new ComparatoreDescrizioni< ComparatoreDescrizioni<String>(s1,s2); String>(s1,s2); String risultato= prova.inizioComune (); prova.inizioComune(); System.out.println (risultato); System.out.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 Fondamenti di Programmazione Un altro main che la utilizza public class MainComparatoreDatiPersonali { . . . public static void main (String [] args) args) { DatiPersonali dati1 = creaDati(); creaDati(); DatiPersonali dati2 = creaDati(); creaDati(); ComparatoreDescrizioni <DatiPersonali> DatiPersonali> prova = new ComparatoreDescrizioni< ComparatoreDescrizioni<DatiPersonali> DatiPersonali> (dati1,dati2); String risultato = prova.inizioComune (); prova.inizioComune(); . . . } . . . Il tipo generico T viene sostituito da DatiPersonali in questo utilizzo Fondamenti di Programmazione Strutture dati + Generics 40 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 41 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 42 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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[] copyOf(T[] original, original, int newLength) newLength) Copies the specified array, array, truncating or padding with nulls (if necessary) necessary) so the copy has the specified length. length. Fondamenti di Programmazione Strutture dati + Generics 43 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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à Fondamenti di Programmazione Strutture dati + Generics 44 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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> Fondamenti di Programmazione 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 <> Fondamenti di Programmazione Strutture dati + Generics 45 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Il carattere “jolly” (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) Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 46 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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( addAll(Collection<? Collection<? extends E> c) Adds all of the elements in the specified collection to this collection (optional operation). operation). La Collection c deve contenere elementi di una sottoclasse di E Fondamenti di Programmazione 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( containsAll(Collection<?> Collection<?> c) Returns true if this collection contains all of the elements in the specified collection. collection. La Collection c può contenere elementi di qualunque classe Fondamenti di Programmazione Strutture dati + Generics 47 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Roadmap • 13. Strutture dati + Generics ¾ Tipi di dati parametrici ¾ Le interface Collection, Collection, Iterable, Iterable, Iterator ¾ Famiglie di Collection: Collection: Set, List, Queue, 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 Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 48 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 49 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione Lista circolare doppia I riferimenti tra gli elementi realizzano un doppio anello Fondamenti di Programmazione Strutture dati + Generics 50 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Doppio riferimento In tutte le varianti, oltre che il riferimento alla testa si può mantenere il riferimento anche all’ultimo elemento (coda) della lista Fondamenti di Programmazione Strutture dati ricorsive • Gli elementi di strutture dati concatenate sono tipicamente definiti in modo ricorsivo: 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; . . . } Fondamenti di Programmazione Strutture dati + Generics 51 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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>) 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 (inner class) Fondamenti di Programmazione 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 Fondamenti di Programmazione Strutture dati + Generics 52 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Classi Lista e NodoLista La classe Lista è parametrica rispetto ad un tipo E public class Lista<E> In caso di lista semplice è sufficiente { il solo attributo testa che riferisce il private NodoLista testa; primo elemento della lista private int numeroElementi; 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 NodoLista è interna a Lista. Essa non è esplicitamente generica ma la sua definizione può } contenere la variabile di tipo E della classe esterna. Fondamenti di Programmazione La classe NodoLista private class NodoLista { private E contenuto; private NodoLista prossimo; public NodoLista (E _contenuto) { contenuto = _contenuto; prossimo = null; 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; } } Fondamenti di Programmazione Strutture dati + Generics 53 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola La classe NodoLista private class NodoLista {. . . public String toString() toString() { return contenuto.toString (); contenuto.toString(); } La stringa descrittiva di un nodo coincide con la stringa descrittiva del suo contenuto } Fondamenti di Programmazione La classe Lista: costruttori public class Lista<E> { private NodoLista testa; private int numeroElementi; numeroElementi; public Lista() { testa = null; null; numeroElementi=0; 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) primoElemento) { testa = new NodoLista( NodoLista(primoElemento); primoElemento); numeroElementi=1; numeroElementi=1; } . . . } Fondamenti di Programmazione Strutture dati + Generics 54 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola La classe Lista: metodi di utilità public class Lista<E> { . . . public boolean isEmpty() isEmpty() { return testa == null; null; } La lista è vuota se il primo riferimento è null public String toString() toString() { if (isEmpty()) isEmpty()) All’interno del metodo toString si ha il tipico { modo di scorrere tutti gli elementi di una return MESS_VUOTA; } lista semplice else { StringBuffer risultato = new StringBuffer(); StringBuffer(); NodoLista corrente = testa; Viene fissato un riferimento all’inizio della lista while (corrente != null) null) La scansione termina quando il riferimento diventa null { risultato.append (corrente.toString toString() () + "\ risultato.append(corrente. "\n"); corrente = corrente.prossimo; L’avanzata avviene passando al prossimo elemento } return risultato.toString (); risultato.toString(); } } Fondamenti di Programmazione Scansione di una lista testa corrente null contenuto contenuto contenuto contenuto contenuto next next next next next=null Fondamenti di Programmazione Strutture dati + Generics 55 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Inserimento in testa public void inserimentoInTesta(E inserimentoInTesta(E contenuto) { Il numero di elementi cresce e il nuovo numeroElementi++; numeroElementi++; NodoLista sarà riferito da testa in ogni caso if (isEmpty()) isEmpty()) testa = new NodoLista(contenuto); NodoLista(contenuto); Se la lista è vuota, il nuovo nodo è anche l’unico e avrà null come successore else testa = new NodoLista(contenuto, NodoLista(contenuto, testa); Altrimenti avrà come successore quello che prima } era in testa Fondamenti di Programmazione Inserimento in coda public void inserimentoInCoda(E inserimentoInCoda(E contenuto) Se la lista è vuota, { numeroElementi++; numeroElementi++; testa e coda coincidono if (isEmpty()) isEmpty()) testa = new NodoLista(contenuto); NodoLista(contenuto); else Si porta il riferimento corrente all’ultimo elemento { NodoLista corrente = testa; while(corrente.prossimo while(corrente.prossimo != null) null) corrente=corrente.prossimo; corrente.prossimo=new NodoLista(contenuto); NodoLista(contenuto); } } . . . e si aggiunge lì il nuovo nodo Fondamenti di Programmazione Strutture dati + Generics 56 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Inserimento in una posizione public void inserimento(int inserimento(int posizione, E contenuto) throws ArrayIndexOutOfBoundsException { if (posizione < 0 || posizione > numeroElementi) numeroElementi) throw new ArrayIndexOutOfBoundsException(); ArrayIndexOutOfBoundsException(); Controllo che la posizione sia valida if (posizione == 0) inserimentoInTesta(contenuto); inserimentoInTesta(contenuto); else Sappiamo già gestire le if (posizione == numeroElementi) numeroElementi) posizioni estreme inserimentoInCoda(contenuto); inserimentoInCoda(contenuto); else { Si porta il riferimento corrente NodoLista corrente = testa; all’elemento in posizione for (int i=1; i < posizione; i++) precedente a quello nuovo corrente=corrente.prossimo; corrente.prossimo=new NodoLista(contenuto,corrente.prossimo); NodoLista(contenuto,corrente.prossimo); } numeroElementi++; … e si aggiunge lì il nuovo nodo numeroElementi++; } Fondamenti di Programmazione Rimozione in testa public E rimozioneInTesta() rimozioneInTesta() throws NoSuchElementException Controllo che ci sia almeno un elemento da togliere { if (isEmpty()) isEmpty()) throw new NoSuchElementException(); NoSuchElementException(); E risultato = testa.contenuto; Metto via il risultato testa=testa.prossimo; Spostando avanti il riferimento ottengo un’eliminazione implicita numeroElementi-; numeroElementi--; return risultato; } Fondamenti di Programmazione Strutture dati + Generics 57 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Rimozione in coda public E rimozioneInCoda() throws NoSuchElementException rimozioneInCoda()throws { Controllo che ci sia almeno un elemento da togliere if (isEmpty()) isEmpty()) throw new NoSuchElementException(); NoSuchElementException(); Se l’elemento è uno solo so if (numeroElementi == 1) return rimozioneInTesta(); rimozioneInTesta(); già come fare else { Porto il riferimento corrente sul penultimo elemento NodoLista corrente = testa; for (int i=1; i < numeroElementinumeroElementi-1; i++) corrente=corrente.prossimo; Metto via E risultato = corrente.prossimo.contenuto; il risultato corrente.prossimo = null; null; Mettendo a null il riferimento numeroElementi-; numeroElementi--; ottengo un’eliminazione implicita return risultato; } } Fondamenti di Programmazione Rimozione in una posizione public E rimozione (int (int posizione) throws ArrayIndexOutOfBoundsException { if (posizione < 0 || posizione > numeroElementinumeroElementi-1) throw new ArrayIndexOutOfBoundsException(); ArrayIndexOutOfBoundsException(); Controllo che la posizione sia valida if (posizione == 0) return rimozioneInTesta(); rimozioneInTesta(); else if (posizione == numeroElementinumeroElementi-1) return rimozioneInCoda(); rimozioneInCoda(); else { NodoLista corrente = testa; for (int i=1; i < posizione; i++) corrente=corrente.prossimo; Sappiamo già gestire le posizioni estreme Si porta il riferimento corrente all’elemento in posizione precedente a quello da eliminare E risultato = corrente.prossimo.contenuto; corrente.prossimo=corrente.prossimo.prossimo; numeroElementi-; numeroElementi--; L’eliminazione si ottiene return risultato; } scavalcamento con uno } Fondamenti di Programmazione Strutture dati + Generics 58 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola 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 Fondamenti di Programmazione Albero binario Fondamenti di Programmazione Strutture dati + Generics 59 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Albero binario A O P U R B E G C B C A G O P U R P R U G A B B P U Fondamenti di Programmazione 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<? Comparable<? super E>> { private TreeNode root; root; . . . private class TreeNode { private E value; value; private TreeNode left; left; private TreeNode right; right; . . . } Poiché l’interface Comparable è a sua volta generica, si specifica il relativo tipo. In pratica si indica che E può estendere Comparable direttamente o anche ereditandola da una superclasse 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 Fondamenti di Programmazione Strutture dati + Generics 60 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola La classe BTree public class BTree <E extends Comparable<? Comparable<? super E>> { private TreeNode root; root; public BTree() BTree() { root = null; null; } L’albero viene costruito inizialmente vuoto public void insertNode(E insertNode(E newElement) newElement) { Per l’inserimento di un nodo: if ( root == null ) - se l’albero è vuoto si crea root = new TreeNode( TreeNode(newElement); newElement); semplicemente la radice else - altrimenti si invoca sulla root. root.insert( insert(newElement); newElement); radice un metodo di } inserimento della classe TreeNode Fondamenti di Programmazione La classe TreeNode private class TreeNode { private E value; value; private TreeNode left; left; private TreeNode right; right; public TreeNode (E _value ) _value) { value = _value ; _value; left = null; null; right = null; null; } public String toString() toString() { return value. value.toString(); toString(); } Fondamenti di Programmazione Strutture dati + Generics 61 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Metodo di inserimento "ordinato" in TreeNode public void insert(E insert(E newElement) newElement) Il nuovo elemento è { if (newElement. newElement.compareTo( compareTo(value) value) < 0) minore del valore di questo nodo: si va a sinistra { Se a sinistra non c’è nulla, if (left == null) null) left = new TreeNode( TreeNode(newElement); newElement); si crea lì un nuovo nodo else Altrimenti si invoca ricorsivamente left. left.insert( insert(newElement); newElement); il metodo sul figlio di sinistra } else if (newElement. newElement.compareTo( compareTo(value) value) > 0)Il caso di elemento maggiore è del tutto { analogo if (right == null) null) right = new TreeNode( TreeNode(newElement); newElement); else right. right.insert( insert( newElement ); Se newElement è un } doppione, viene ignorato } Fondamenti di Programmazione Ricerca di un elemento (nella classe BTree) public boolean binarySearch(E binarySearch(E toFind) toFind) Il metodo di ricerca { return searchHelper( searchHelper(root, root, toFind); toFind); pubblico si avvale di un metodo ricorsivo privato } private boolean searchHelper( searchHelper(TreeNode node, node, E wanted) wanted) { Se nella ricerca si arriva a null, if (node == null) null) return false;l’elemento cercato non è presente if (wanted. wanted.compareTo( compareTo(node. node.value) value) == 0) return true; true; Qui abbiamo trovato l’elemento if (wanted. wanted.compareTo( compareTo(node. node.value) value) < 0) return searchHelper( searchHelper(node. node.left, left, wanted); wanted); else return searchHelper( searchHelper(node. node.right, right, wanted); wanted); } La ricerca, se non è terminata, prosegue ricorsivamente a sinistra o a destra Fondamenti di Programmazione Strutture dati + Generics 62 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Traversata in ordine public void inOrderTraversal() inOrderTraversal() { inOrderHelper( inOrderHelper(root); root); } private void inOrderHelper( inOrderHelper(TreeNode node) node) { if (node == null) null) return; inOrderHelper( inOrderHelper(node. node.left); left); System.out.println (node. System.out.println( node.toString()); toString()); //o qualunque altra operazione inOrderHelper( inOrderHelper(node. node.right); right); } Fondamenti di Programmazione Traversata in preordine public void preOrderTraversal() preOrderTraversal() { preOrderHelper( preOrderHelper(root); root); } private void preOrderHelper( preOrderHelper(TreeNode node) node) { if (node == null) null) return; System.out.println (node. System.out.println( node.toString()); toString()); //o qualunque altra operazione preOrderHelper( preOrderHelper(node. node.left); left); preOrderHelper( preOrderHelper(node. node.right); right); } Fondamenti di Programmazione Strutture dati + Generics 63 Modulo di Fondamenti di Programmazione P. Baroni - P.Martinelli - M. Rossi - A. Viola Traversata in postordine public void postOrderTraversal() postOrderTraversal() { postOrderHelper( postOrderHelper(root); root); } private void postOrderHelper( postOrderHelper(TreeNode node) node) { if (node == null) null) return; postOrderHelper( postOrderHelper(node. node.left); left); postOrderHelper( postOrderHelper(node. node.right); right); System.out.println (node. System.out.println( node.toString()); toString()); //o qualunque altra operazione } Fondamenti di Programmazione Strutture dati + Generics 64