Java Collections – Parte I

In questa prima parte del tutorial sulle collection Java dopo una breve introduzione parleremo di Array e liste di oggetti. Vedremo le varie classi che implementano l’interfaccia Java List con vari esempi che ne spiegano l’utilizzo.


Nel tutorial Oracle una collection è definita come:

A collection — sometimes called a container — is simply an object that groups multiple elements into a single unit. Collections are used to store, retrieve, manipulate, and communicate aggregate data. Typically, they represent data items that form a natural group, such as a poker hand (a collection of cards), a mail folder (a collection of letters), or a telephone directory (a mapping of names to phone numbers).

In Java il framework che permette di gestire le collection mette a disposizione:

interfacce
definizioni di interfacce che permettono di manipolare collezioni di oggetti indipendentemente dai dettagli implementativi
implementazioni
classi concrete che implementano le varie interfacce
algoritmi
funzionalità riusabili per eseguire operazioni comune (per esempio la ricerca o l’ordinamento)

Array

Gli Array non sono contenuti nel framework delle collection, ma a volte possono essere utilizzati al posto di una collection. Gli Array sono oggetti ma non contengono metodi utili a parte il campo in sola lettura length. Di solito vengono usati quando si conosce il numero massimo degli oggetti contenuti, infatti sono a dimensione fissa: se si cerca di accedere una posizione errata viene generata una ArrayIndexOutOfBoundException.

Gli Array sono tipati ma ci possono essere comunque dei problemi runtime:

Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in"; // Throws ArrayStoreException

In ambito mobile (per esempio su Android) viene fatto un largo uso degli Array per questioni di performance. Solitamente utilizzando gli Array si scrive codice più performante e che occupa meno memoria rispetto allo stesso codice scritto usando le collection.

Un esempio di utilizzo degli Array è il seguente (da notare che possono essere creati sia Array di tipi primitivi che di oggetti):

int[] arrayInt = new int[10];
arrayInt[5] = 12345;
System.out.println(arrayInt.length);
for (int i = 0; i < arrayInt.length; i++) {
    System.out.println(arrayInt[i]);
}
Persona[] arrayPersone = new Persona[10];
System.out.println(arrayPersone.length);
for (int i = 0; i < arrayPersone.length; i++) {
    System.out.println(arrayPersone[i]);
}
for (int i = 0; i < 10; i++) {
    arrayPersone[i] = new Persona(i, "nome" + i, "cognome" + i);
}
for (int i = 0; i < arrayPersone.length; i++) {
    System.out.println(arrayPersone[i]);
}

Interfacce principali

Le principali interfacce sono Collection, List, Set e Map. Le interfacce List e Set estendono Collection:


Collections in Java

Per scrivere codice indipendente dall'implementazione e aumentare la riusabilità è sempre buona norma utilizzare l'interfaccia più generica possibile per definire variabili, campi e parametri di metodi.

Prendiamo per esempio il seguente metodo:

public boolean isBig(ArrayList< ?> l) {
	return l.size() > 100;
}

Il parametro è definito come ArrayList, ma in realtà usa solo un metodo definito nell'interfaccia Collection. Definendo il parametro come di tipo Collection il metodo può essere usato non solo con un ArrayList ma anche con tutte le altre implementazioni di Collection.

Stesso discorso può essere fatto per i metodi get di una classe: ritornare una interfaccia generica permette di cambiare l'implementazione utilizzata in un secondo momento senza impatti sulle classi che utilizzano la classe modificata.

Collection

Collection è l'interfaccia più generica, non definisce nè l'ordine in cui sono memorizzati gli elementi nè se ci possono essere elementi duplicati.

A differenza degli Array tutte le collection non possono contenere tipi primitivi ma solamente oggetti, per inserire tipi primitivi è necessario usare un oggetto wrapper (per esempio Integer al posto di int):

public void test(Collection c) {
	c.add(new Integer(1));
	c.add(new Integer(5));
	c.add(new Integer(7));
	c.remove(new Integer(5));
	System.out.println(c.size() == 2);
}

A partire da Java 1.5 c'è la conversione automatica (auto boxing e unboxing) dei wrapper in tipi primitivi che semplifica la scrittura del codice:

public void test(Collection c) {
	c.add(1);
	c.add(5);
	c.add(7);
	c.remove(5);
	System.out.println(c.size() == 2);
}

List

Un oggetto List contiene oggetti ordinati in base all'ordine di inserimento, può contenere duplicati e permette di inserire e ottenere gli elementi in base all'indice. E' la versione "evoluta" di un Array in quanto non contiene la limitazione della dimensione massima prefissata.

public void test(List c) {
	c.add("aaa");
	c.add("bbb");
	c.add(1, "ccc");
	c.remove("aaa");
	System.out.println(c.get(1).equals("bbb"));
}

ArrayList

ArrayList è l'implementazione di List che memorizza gli elementi in un Array, si occupa della gestione della dimensione dell'Array in modo trasparente per lo sviluppatore. Ogni volta che l'Array è pieno viene invocato il metodo ensureCapacity che crea un nuovo Array di dimensione (old *3) / 2 +1 in cui vengono copiati tutti gli elementi presenti usando il metodo Arrays.copyOf.

Per evitare ridimensionamenti se si conosce già la dimensione massima può essere specificata nel costruttore.

Visto che i dati sono memorizzati in un Array l'accesso all'i-esimo elemento è veloce. Per lo stesso motivo l'operazione di rimozione di un elemento è lenta, tutti gli elementi successivi all'elemento da eliminare devono essere spostati di una posizione.

LinkedList

LinkeList è l'implementazione di List che usa una lista linkata bidirezionale, permette di scorrere gli elementi partendo sia dall'inizio che dalla fine.
Accedere all’i-esimo elemento è un’operazione lenta, infatti devono essere percorsi tutti gli elementi precedenti; l’operazione di rimozione è invece veloce, in quanto non devono essere spostati oggetti, ma prevede solo il cambiamento di alcuni collegamenti fra i nodi della lista.

Vector

Vector è una implementazione di List ormai obsoleta (risale al jdk 1.0), è simile ad ArrayList ma sincronizzata (può essere usata da più thread paralleli senza problemi di concorrenza). Anche il JavaDoc di Vector ne sconsigla l'utilizzo:

Unlike the new collection implementations, Vector is synchronized. If a thread-safe implementation is not needed, it is recommended to use ArrayList in place of Vector

Anche nel caso si voglia manipolare una collection da thread diversi può essere usata una qualsiasi Collection che può essere resa sincronizzata usando il metodo Collections.synchronizedCollection.

Iterator

La classe Iterator è il modo standard per scorrere gli elementi di una qualunque Collection. Contiene 3 metodi:

hasNext
ritorna true se ci sono ancora elementi da scorrere
next
ritorna il successivo elemento
remove
rimuove dalla collection l'ultimo elemento ritornato. Questo metodo deve essere usato quando si vuole rimuovere un oggetto da una Collection durante un ciclo sugli elementi. Infatti in questi casi chiamare il metodo remove di Collection causa una ConcurrentModificationException al successivo accesso alla lista

Senza iterator una lista potrebbe essere visitata così:

public static int sumLessThen(List l, int max) {
	int tot = 0;
	for (int i = 0; i < l.size(); i++) {
		Integer tmp = l.get(i);
		if (tmp != null && tmp.intValue() < max) {
			tot += tmp;
		}
	}
	return tot;
}

Se la lista passata è una LinkedList questo metodo è poco efficiente (per ogni elemento il metodo get riparte dall'inizio). Usando un iterator il metodo può essere riscritto come:

public static int sumLessThen(List l, int max) {
	int tot = 0;
	for (Iterator iterator = l.iterator(); iterator.hasNext();) {
		Integer tmp = iterator.next();
		if (tmp != null && tmp.intValue() < max) {
			tot += tmp;
		}
	}
	return tot;
}

Da Java 1.5 è stato introdotto un nuovo costrutto for, denominato foreach, che può essere usato con tutti gli oggetti che implementano l'interfaccia Iterable e con gli Array:

List l = Arrays.asList(4, 8, 15, 16, 23, 42);
int tot = 0;
for (Integer i : l) {
	tot += i;
}
Integer[] a = new Integer[6];
a[0] = 4;
a[1] = 8;
a[2] = 15;
a[3] = 16;
a[4] = 23;
a[5] = 42;
tot = 0;
for (Integer i : a) {
	tot += i;
}

Conclusioni

Finisce qui la prima parte di questo tutorial sulle collection in Java; nella seconda parte vedremo nel dettaglio i Set e le Map con vari esempi di utilizzo.

Fabio Collini

Software Architect con esperienza su piattaforma J2EE e attualmente focalizzato principalmente in progetti di sviluppo di applicazioni Android. Attualmente sono in Nana Bianca dove mi occupo dello sviluppo di alcune app Android. Coautore della seconda edizione di Android Programmazione Avanzata e docente di corsi di sviluppo su piattaforma Android. Follow me on Twitter - LinkedIn profile

  • Pingback: Java Collections – Parte II | Cose Non Javiste()

  • Pingback: Tutorial: le HashMap in java | Cose Non Javiste()

  • Cicraf

    c’è da dire (almeno a quanto ho letto su alcuni testi) che una ArrayList sincronizzata ha performance inferiori ad un Vector 

    • Ciao, sicuramente la differenza grossa di performance c’è fra i metodi sincronizzati e non sincronizzati. Non ho mai approfondito delle differenze fra ArrayList e Vector perchè solitamente non mi occupo di programmazione concorrente e quindi difficilmente mi è servito usare liste sincronizzate. Comunque grazie per il consiglio! 

  • Fab

    Salve sono un newbie del java e non è chiara la differenza tra Iterator e Iterable.
    Se Iterator è una classe perchè effettivamente al suo interno ha dei metodi implementati, e Iterable è una interfaccia, perchè sulle api anche Iterator compare come interfaccia? E anche qui ad esempio sono designate come interfaccie –>
    http://www.agentgroup.unimo.it/didattica/curriculum/marco/MAIN/didattica/TecnInternetWeb/java/J5c_Collection.html

    Grazie mille

    • Fab

       mi sono reso conto di aver detto una granda *azzata . Sono entrambe interfacce  ma una è stata creata prima dell’ingresso dei generici (1.5).
      Ad ogni modo gradirei capirne la differenza!! 🙂

      • Ciao, il nome è simile e sono legate fra di loro ma non sono la stessa cosa! Un Iterator permette di scorrere una collection mentre un Iterable rappresenta un oggetto che può essere iterato. Le interfacce sono legate in quanto un oggetto Iterable deve implementare un metodo che ritorna un Iterator. Iterable è legato al ciclo foreach in quanto se un oggetto implementa Iterable può essere ciclato con un foreach.
        Spero di essere stato chiaro anche se la differenza sembra un gioco di parole!

        • fab

           Grazie di cuore davvero. Buon lavoro.

  • Vincenzo

    Ragazzi, premetto che sono un ignorantone in materia, però mi avvalgo del vostro contributo tecnico. Vi spiego ho un blog fatto con blogger, vorrei implementare una funzione interessante che ho trovato su http://iomobile.smartworld.it/come-trasferire-i-contatti-da-sim-a-telefono-samsung-galaxy-157522.html#steps_3 ossia consultare un articolo dalla stessa pagina, semplicemente facendo avanti e indietro con i pulsanti. E’ possibile? Chi mi aiuta? Sarei disposto a compensare chiunque mi da una mano