Le collezioni di oggetti Prof. Accarino IIS Altiero Spinelli via Leopardi 132 Sesto San Giovanni Contenitori di oggetti Java mette a disposizione una libreria per gestire gruppi di oggetti organizzandola in due tipologie: Collection: una raccolta sequenziale di singoli elementi ai quali sono applicate una o più regole (List, Set, Queue) Map: un gruppo di coppie chiave-valore indicanti oggetti, permettendo di recuperare un valore mediante la chiave ad esso associata La distinzione si basa sul numero degli elementi contenuti in ciascuna posizione del contenitore Collection Una Collection è un oggetto che raggruppa elementi multipli in una singola unità Viene utilizzata per memorizzare, recuperare e manipolare dati, per trasmetterli da un metodo ad un altro Tipicamente rappresentano dati correlati tra loro, come una collezione di numeri telefonici, collezione di lettere, etc. Sono state introdotte a partire dalla release 1.2 (collection framework) Collection Framework Il framework dei contenitori è composto da: Interfacce: Implementazioni: Tipi di dato astratti che rappresentano le classi contenitori Permettono di manipolare i contenitori indipendentemente dai dettagli della rappresentazione In genere formano una gerarchia Implementazioni concrete delle interfacce Sono le strutture dati riusabili Algoritmi Metodi che effettuano delle computazioni sui contenitori, come ad esempio ordinamento, ricerca, … Gli algoritmi sono polimorfici poiché gli stessi metodi possono essere applicati a differenti implementazioni Sono le funzionalità riusabili Gerarchia di collection FrameWork Interfacce Set List Collezione che non può contenere duplicati Astrazione dell’insieme matematico Collezione ordinata (detta anche sequenza) Può contenere elementi duplicati Si accede agli elementi mediante un indice intero (posizione) Queue Rappresenta una coda di elementi,l'approccio è rivolto a risolvere problemi di sincronizzazione di programmi concorrenti e il loro studio esula da questo contesto Interfacce SortedSet Insieme dove gli elementi sono ordinati in ordine ascendente Operazioni aggiuntive per utilizzare l’ordinamento Deque (double ended queue) Coda che permette l’inserimento e la rimozione di elementi sia in testa che in coda Utilizzo di Collection non parametrizzate Le collection in Java contengono oggetti di tipo Object e E’ necessario effettuare un casting quando si recupera l’oggetto E’ possibile contenere tipi eterogenei Non è possibile inserire valori di tipi primitivi E’ necessario utilizzare tipi wrapper: Integer, Long ecc. Esempio: ArrayList al= new ArrayList(); al.add(“Pippo”); al.add(new Integer(5)); String s=(String)al.get(0); Integer numero = (Integer)dati.get(0); int n = numero.intValue(); Svantaggi nell’utilizzo di liste non Parametrizzate Uno svantaggio tipico è la perdita di informazione sul tipo quando si inseriscono oggetti nel contenitore. Infatti: poiché non vi sono restrizioni sul tipo di oggetti inseribili nel contenitore, è possibile inserire senza problemi cani in una collezione di gatti (fonte di potenziali errori) a causa della perdita del tipo, occorre eseguire una forzatura (cast) “corretta”, a run time, all’atto dell’estrazione dell’oggetto per non incorrere in un’eccezione Esempio: import java.util.*; class Gatto { public class CaniEGatti { // costruttore public Gatto(int i) // restituisce l ’ identificativo unico public static void main(String[] args) { List gatti = new ArrayList(); public int getID() } for(int i = 0; i < 7; i++) gatti.add(new Gatto(i)); public class Cane { gatti.add(new Cane(7)); // costruttore /* Eccezione rilevata a run time */ public Cane(int i) for(int i = 0; i < gatti.size(); i++) ((Gatto)gatti.get(i)).id(); // restituisce l ’ identificativo unico public int getID() } } } Parametrizzare la lista Per ovviare ai problemi precedenti è lecito definire una nuova classe lista parametrizzata List<Gatto> gatti = new ArrayList<Gatto>(); List<Cane> cani = new ArrayList<Cane>(); for (int i = 0; i < 7; i++) // aggiungi gatti.add(new Gatto(i)); for (int i = 0; i < 3; i++) // aggiungi cani.add(new Cane(i)); Vantaggi nell’uso di generici I principali vantaggi dovuti all’introduzione dei tipi generici sono: Il compilatore può verificare qualsiasi operazione add di oggetti alla collezione il tipo dell’oggetto estratto da una collezione è noto, quindi non vi è la necessità di operare un cast a un tipo diverso (fonte di potenziali errori se il cast è fatto su tipi non omologhi) Implementazioni di Vettori Le classi java.util.Vector e java.util.ArrayList definiscono degli oggetti, chiamati vettori, che consentono di rappresentare sequenze di oggetti di lunghezza variabile. Ciascun oggetto in un vettore ha un numero intero, detto indice, che ne indica la posizione nel vettore. Grazie all'indice è possibile accedere indipendentemente a ciascun elemento della sequenza. L'accesso ad una posizione inesistente provoca un errore (viene lanciata un'eccezione). Al momento della dichiarazione o creazione di un vettore, il tipo base degli elementi del vettore va indicato tra parentesi angolate dopo il nome della classe (Vector e ArrayList sono classi generiche). Ad esempio, per costruire un vettore di stringhe (inizialmente vuoto) e assegnarlo alla variabile vet, scriveremo: ArrayList<String> vet = new ArrayList<String>(); Implementazioni di Vettori I vettori sono dunque simili agli array. Le differenze principali sono due: la dimensione di un vettore può variare durante l'esecuzione di un programma; , , ... il tipo base di un vettore NON può essere un tipo primitivo (int double ). L'implementazione dei vettori è basata sugli array. Al momento della creazione di un vettore, alla variabile di istanza elementData viene assegnato un array di oggetti la cui dimensione dipende dal costruttore utilizzato: String[ ] elementData = new String[initialCapacity]; Successivamente, se la capacità dell’array non è più sufficiente, viene creato un nuovo array più grande nel quale vengono copiati tutti gli elementi del vecchio. Realizzazione in memoria di Vettori Differenze tra Vector e ArrayList Nel seguito, usando la sintassi Java 6.0, indicheremo con Vector<E> e ArrayList<E> le classi generiche che definiscono i vettori quando il tipo base non è specificato. Si noti che <E> può essere considerata una variabile di tipo, che può essere istanziata con qualunque classe Java. Le classi generiche Vector<E> e ArrayList<E> sono sostanzialmente equivalenti, ma: I metodi di Vector<E> sono sincronizzati, mentre quelli di ArrayList<E> non lo sono. Quindi se il programma èconcorrente (cioè usa il multi-threading di Java) è opportuno usare Vector<E>, altrimenti conviene sempre ArrayList<E> perchè più efficiente. Vector<E> fornisce, con opportuni metodi e costruttori, un controllo maggiore sulla capacità, cioè la dimensione dell'array soggiacente. Per motivi storici, Vector<E> fornisce più metodi con nomi diversi per manipolare gli elementi di un vettore. •In genere conviene abituarsi ad usare sempre la classe ArrayList Classi Vector<E> e ArrayList<E>: costruttori I costruttori di Vector<E> permettono di specificare la capacità iniziale del vettore (initialCapacity) e il valore da usare per aumentarela capacità (capacityIncrement) quando necessario. Se (capacityIncrement == 0), il nuovo array avrà capacità doppia rispetto all'attuale. /* crea un vettore vuoto, con i parametri specificati */ Vector (int initialCapacity, int capacityIncrement) /* per default, capacityIncrement=0 */ Vector (int initialCapacity) /* per default, initialCapacity = 10 e capacityIncrement = 0 */ Vector () I costruttori di ArrayList<E> permettono di specificare solo la capacità iniziale del vettore. /* crea un vettore con la capacita' iniziale indicata */ ArrayList (int initialCapacity) /* crea un vettore vuoto; la capacita' iniziale non e' specificata */ ArrayList () Come usare i Vettori Si usa preferibilmente quando un array •I dati sono tipi elementari; •La lunghezza dell'array è nota al momento della creazione; •La lunghezza dell'array non varia dinamicamente. ArrayList •I dati non sono tipi elementari o si usa un tipo involucro; •La lunghezza non è nota al momento della creazione; •La lunghezza varia dinamicamente; Vector Solo per compatibilità con programmi vecchi. Principali metodi di ArrayList boolean add(Object element) void add(int index, Object element) Rimuove tutti gli elementi dalla lista. Iterator iterator() Sostituisce l’elemento dell’indice specificato con l’elemento specificato. void clear() Ritorna l’elemento della lista dell’indice specificato. Object set(int index, Object element) Inserisce l’elememnto nella posizione specificata dall’indice. Object get(int index) Aggiunge l’elemento alla fine della lista. Ritorna un Iteratore per scorrere gli elementi in sequenza. int size() Ritorna il numero degli elementi della lista. Iterator Modo “universale” per scorrere collezioni di elementi, indipendentemente dalla particolare disposizione degli elementi Il metodo Iterator iterator() è disponibile in tutte le classi che estendono la Collection Iterator boolean hasNext() Object next() Ritorna true se l’iterazione contiene ulteriori elementi. Ritorna il prossimo elemento dell’iterazione. void remove() Rimuove dalla collezione l’ultimo elemento iterato (optional operation). Come funziona un Iteratore Esercizio Scrivere un metodo java int somma(ArrayList a) che somma gli elementi del vettore di interi utilizzando gli iteratori. 5 3 4 8 2 Somma: 22 Ciclo di scorrimento: Si ricava l’iteratore dall’ArrayList Si utilizza un ciclo while che utilizza come condizione Il metodo hasNext() dell’iteratore per continuare a ciclare, Si usa next() per avere l’elemento corrente Esempio public static int somma(ArrayList a){ int somma=0; Iterator i=a.iterator(); while (i.hasNext()){ Integer val=(Integer)i.next(); somma=somma+val.intValue(); } return somma; } Il costrutto foreach in java Il foreach è un particolare costrutto (disponibile a partire da Java 5) che ci permette di iterare velocemente una struttura dati. In java possiamo usarlo sia su un array oppure su oggetti di tipo Iterable, cioè che dispongono di un iteratore (java.util.Iterator). Le Collections di java (Linkedlist, ArrayList, etc.) sono tutti oggetti di tipo Iterable. il costrutto generico è il seguente : for(type var : array) { //ciclo } Esempio somma elementi ciclo for e forech Ciclo For Ciclo Forech public static int somma(int [] array) { int somma = 0; for(int i = 0; i < array.length; i++) { somma += array[i]; } return somma; } public static int somma(int [] array) { int somma = 0; for(int element:array) somma+=element; return somma; } Iterazione di un ArrayList ArrayList <String> lista = new ArrayList<String>(); lista.add("a"); lista.add("b"); .... Iterator Ciclo Forech Iterator <String> it = lista.iterator(); while(it.hasNext()) { String str = it.next(); ... } for(String str:lista) { ..... } LinkedList Rappresenta una lista di oggetti doppiamente concatenata in cui ogni nodo punta sia al suo predecessore che al suo successore Principali metodi di LinkedList Creare una pila con LinkedList public class Pila { private LinkedList list; public Pila() { list=new LinkedList(); } public void push(Object o) { list.addFirst(o); } public Object pop() { return list.removeFirst(); } public int size() { return list.size();} } Creare una coda con LinkedList public class Coda { private LinkedList list; public Coda() { list=new LinkedList(); } public void push(Object o) { list.addLast(o); } public Object pop() { return list.removeFirst(); } public int size() { return list.size();} } la classe Stack<E> In Java la classe Stack estende Vector, implementando direttamente i metodi: •empty() Controllo se la pila è vuota oppure no •peek() Lettura dell'elemento in cima alla pila, senza eliminarlo •pop() Estrazione dell'elemento in cima alla pila •push(E item) Inserimento di un elemento in cima alla pila ed eredita da Vector (tra gli altri) il metodo clear(). Il fatto di estendere Vector permette di usare anche tutti i suoi metodi, in particolare l'accesso diretto agli elementi, (possibilità poco elegante ma spesso comoda). la classe ArrayDeque<E> In informatica, una deque (solitamente pronunciato come deck, è l'abbreviazione di double-ended queue, cioè coda doppia) è una struttura dati astratta simile a una lista, anche chiamata lista concatenata testa-coda in quanto gli elementi possono essere aggiunti o rimossi solamente dalla testa o dalla coda. La classe ArrayDeque implementa una coda a doppia entrata I principali metodi sono addFirst(e) Inserisce e all’inizio del deque addLast(e) Inserisce e alla fine del deque removeFirst() Restituisce e rimuove il primo elemento del deque removeLast() Restituisce e rimuove l’ultimo elemento del deque getFirst() Restituisce il primo elemento del deque getLast() Restituisce l’ultimo elemento del deque size() Restituisce il numero di elementi del deque isEmpty() Restituisce true se il deque è vuoto e false altrimenti