Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Java Collections Framework

Analizziamo le caratteristiche del framework e studiamo le principali classi concrete che permettono di gestire al meglio le nostre collezioni di oggetti
Analizziamo le caratteristiche del framework e studiamo le principali classi concrete che permettono di gestire al meglio le nostre collezioni di oggetti
Link copiato negli appunti

Le soluzioni per la gestione di un insieme di oggetti in Java sono molteplici; come sempre, a seconda delle circostanze, bisogna individuare la soluzione migliore da adottare. Tutte le classi e le interfacce che permettono di gestire gruppi di oggetti, costituiscono il Java Collection Framework. In questo articolo analizziamo le caratteristiche del framework e studiamo le principali classi concrete che permettono di gestire al meglio le nostre collezioni di oggetti.

Figura 1. Diagramma
Diagramma

Il Java Collection Framework è costituito dai seguenti elementi:

  1. le interfacce che definiscono le operazioni classiche di una generica collezione di oggetti.
  2. le classi concrete che implementano le interfacce utilizzando differenti tipi di strutture dati.
  3. gli algoritmi di ricerca e di ordinamento utilizzati dalle classi concrete.

Le interfacce possono essere suddivise in due macro-categorie:

  1. le Collection che sono ottimizzate per operazioni di inserimento, modifica e cancellazione di elementi all'interno di un insieme di oggetti;
  2. le Map che sono ottimizzate per operazioni di ricerca.

I metodi delel Collection sono i seguenti:

  • public boolean add(Object o): aggiunge un oggetto alla collection;
  • public boolean addAll(Collection c): aggiunge una collection di oggetti alla collection considerata;
  • public void clear(): svuota la collection;
  • public boolean contains(Object o): verifica l'esistenza di un oggetto all'interno della collection;
  • public boolean containsAll(Collection c): verifica l'esistenza di una collection all'interno della collection considerata;
  • public boolean isEmpty(): verifica se la collection è vuota;
  • public Iterator iterator(): restituisce un'istanza della classe Iterator che permette di scorrere la lista;
  • public boolean remove(Object o): rimuove un oggetto dalla collection;
  • public boolean removeAll(Collection c): rimuove una collection dalla collection considerata;
  • public boolean retainAll(Collection c);
  • public int size(): restituisce il numero di elementi presenti nella collection;
  • public Object[] toArray(): restituisce la collection sottoforma di array;
  • public Object[] toArray(Object[] a): restituisce la collection sottoforma di array.

Ci sono inoltre delle interfacce che estendono l'interfaccia Collection, definendo ulteriori metodi con caratteristiche più specifiche:

  • l'interfaccia Set che permette di gestire collezione di oggetti non duplicati identificabili univocamente mediante il metodo equals();
  • l'interfaccia List che permette di gestire collezione di oggetti ordinati identificabili univocamente mediante un indice che rappresenta la sua posizione all'interno della lista;
  • l'interfaccia Queue che permette di gestire collezione di oggetti gestiti con la filosofia FIFO (first-in, first-out) in modo che il primo oggetto inserito sia il primo candidato ad essere letto.

I metodi delle Map invece sono:

  • public void clear(): permette di svuotare la map;
  • public boolean containsKey(Object arg0): verifica l'esistenza di un oggetto all'interno della map in base alla sua chiave;
  • public boolean containsValue(Object arg0): verifica l'esistenza di un oggetto all'interno della map in base al suo valore;
  • public Set entrySet(): restituisce la map sottoforma di Set;
  • public Object get(Object arg0): restituisce l'oggetto in base alla sua chiave;
  • public boolean isEmpty(): verifica se la map è vuota;
  • public Set keySet(): restituisce le chiavi della map sottoforma di Set;
  • public Object put(Object arg0, Object arg1): aggiunge un oggetto alla map;
  • public void putAll(Map arg0): aggiunge una map di oggetti alla map considerata;
  • public Object remove(Object arg0): rimuove un oggetto dalla map;
  • public int size(): restituisce il numero di elementi presenti nella map;
  • public Collection values(): restituisce la map sottoforma di Collection.

Analizziamo adesso le principali classi concrete del framework, quelle che vengono effettivamente utilizzate per gestire le proprie collezioni di oggetti. Per ciascuna di esse, faremo un piccolo esempio cercando di evidenziare sia lati positivi che quelli negativi.

Per gli esempi utilizzeremo la seguente classe Persona che dispone, oltre ai classici metodi get e set, dei seguenti metodi:

  • ArrayList;
  • Vector;
  • HashSet;
  • HashMap;
  • Hashtable.

Listato 1. Classe di esempio

private String nome;
private String cognome;
private int eta;

public Persona(){}

public Persona(int eta, String nome, String cognome){
  setEta(eta);
  setNome(nome);
  setCognome(cognome);
}

public String toString() {
  return nome + " " + cognome + " " + eta;
}

public boolean equals(Persona p) {
  if (cognome.equals(p.getCognome()) && nome.equals(p.getNome()) && eta == p.getEta())
    return true;
  else
    return false;
}

public int hashCode() {
  return cognome.length() + nome.length() + eta;
}

public int compareTo(Persona p) {
  if (getEta() == p.getEta())
    return 0;
  else if (getEta()>p.getEta())
    return 1;
  else
    return -1;
}

ArrayList

Implementa l'interfaccia List. Gli oggetti vengono memorizzati in locazioni di memoria contigue quindi è possibile accedere a ciascun oggetto molto velocemente mediante il suo indice all'interno della collezione. Naturalmente non è adatta per operazioni di ricerca poiché occorrerebbe scorrere tutta la lista per ricercare un oggetto al suo interno. È possibile scorrere facilmente la lista sia mediante la classe iterator che mediante accesso diretto utilizzando l'indice dell'oggetto.

Listato 2. Esempio ArrayList

ArrayList<Persona> lista = new ArrayList<Persona>(0);
lista.add(new Persona(27, "marco", "bianco"));
lista.add(new Persona(80, "luca", "arancio"));
lista.add(new Persona(75, "giovanni", "rossi"));
lista.add(new Persona(29, "mario", "bianchi"));

Iterator<Persona> it = lista.iterator();
while (it.hasNext()) {
  Persona p = it.next();
  System.out.println(p);
}

for (int i=0; i<lista.size(); i++){
  Persona p = lista.get(i);
  System.out.println(p);
}

Vector

Come l'ArrayList implementa l'interfaccia List. È praticamente identico all'ArrayList ma i suoi metodi di accesso agli oggetti contenuti sono sincronizzati quindi è leggermente meno performante ma sicuramente da preferire nel caso in cui sia necessaria la sincronizzazione dei dati.

HashSet

Implementa l'interfaccia Set. Non ammette duplicati e utilizza l'hashCode degli oggetti inseriti per identificarli univocamente. Naturalmente è consigliato utilizzare questa implementazione quando è necessario accedere agli oggetti in modo veloce ed è indifferente l'ordine in cui tali oggetti vengono letti quando la collezione viene scandita. È possibile scorrere facilmente la hashSet mediante la classe iterator ma non è possibile conoscere a priori l'ordine in cui gli oggetti saranno letti.

Listato 3. Esempio di HashSet

HashSet<Persona> hash = new HashSet<Persona>(0);
hash.add(new Persona(27, "marco", "bianco"));
hash.add(new Persona(80, "luca", "arancio"));
hash.add(new Persona(75, "giovanni", "rossi"));
hash.add(new Persona(29, "mario", "bianchi"));

Iterator<Persona> it = hash.iterator();
while (it.hasNext()){
  Persona p = it.next();
  System.out.println(p);
}

HashMap

Implementa l'interfaccia Map. Gli oggetti vengono memorizzati in una tabella hash e vengono identificati mediante il proprio hashCode. Naturalmente è consigliato utilizzare questa implementazione quando è necessario accedere agli oggetti in modo veloce ed è indifferente l'ordine in cui tali oggetti vengono letti quando la collezione viene scandita oppure quando è necessario ricercare un oggetto a partire dalla sua chiave. Ciascun elemento è associato ad una chiave. Per scandire la hashmap è necessario ottenere prima una collection e poi ciclare questa con il classico iterator. È possibile ricercare facilmente un oggetto all'interno della hashmap a partire dalla sua chiave univoca.

Listato 4. Esempio HashMap

HashMap<String, Persona> hash = new HashMap<String, Persona>(0);
hash.put("marco", new Persona(27, "marco", "bianco"));
hash.put("luca", new Persona(80, "luca", "arancio"));
hash.put("giovanni", new Persona(75, "giovanni", "rossi"));
hash.put("mario", new Persona(29, "mario", "bianchi"));

Collection<Persona> collection = hash.values();
Iterator<Persona> it = collection.iterator();
while (it.hasNext()){
  Persona p = it.next();
  System.out.println(p);
}

System.out.println(hash.get("marco"));

Hashtable

Praticamente identica alla hashMap ma l'acesso ai dati è sincronizzato. Inoltre, il Framework dispone di due classi costituite da una serie di metodi statici che permettono di effettuare diverse operazioni sulle collezioni di oggetti: Collections e Arrays i cui metodi principali sono i seguenti:

  • sort(): ordina una collection o un array;
  • binarySearch(): ricerca un oggetto all'interno di una collection o un array.

Infine, il Java Collection Framework mette a disposizione due interfacce mediante le quali è possibile definire il modo in cui ordinare gli oggetti all'interno di un insieme:

  1. Comparable: per implementare questa interfaccia occorre definire esclusivamente il metodo compareTo la cui firma è la seguente: public int compareTo(Object o);
  2. Comparator: per implementare questa interfaccia occorre definire esclusivamente il metodo compare la cui firma è la seguente: public int compare(Object arg0, Object arg1).

Per finire facciamo un piccolo esempio di utilizzo di queste interfacce. Ipotizziamo di dover gestire un insieme di persone. Ciascuna persona ha un proprio nome, un proprio cognome e un'età.

Modifichiamo la precedente classe Persona che adesso implementa l'interfaccia Comparable, cioè definisce un ordinamento tra due oggetti della stessa classe.

Listato 5. Predente esempio con l'implementazione di Comparable

public class Persona implements Comparable<Persona>{
  ..//
  public int compareTo(Persona p){
    if (getEta() == p.getEta())
      return 0;
    else if (getEta()>p.getEta())
      return 1;
    else
      return -1;
  }
}

La classe Persona deve implementare il metodo compareTo. Tale metodo deve restituire un numero intero: 0 se gli oggetti sono uguali, un numero positivo se l'oggetto instanziato è maggiore di quello passato come parametro, un numero negativo nel caso opposto. Nel nostro caso abbiamo gestito l'ordinamento soltanto in base all'età della persona.

Ora creiamo un'altra classe, CognomeComparator, che implementa l'interfaccia Comparator e ci permette di definire l'ordinamento per cognome.

Listato 6. Definisce l'ordinamento del cognome

public class CognomeComparator implements Comparator<Persona>{
  public int compare(Persona p1, Persona p2){
    return p1.getCognome().compareTo(p2.getCognome());
  }  
}

Il metodo compare deve restituire un numero intero: 0 se gli oggetti sono uguali, un numero positivo se l'oggetto instanziato è maggiore di quello passato come parametro, un numero negativo nel caso opposto. Utilizziamo in questo caso il metodo compareTo della classe String che fa proprio al caso nostro.

Ora vediamo come è semplice utilizzare le classi scritte per ordinare la lista secondo le nostre esigenze.

Listato 7. Ordina le liste

public static void main(String[] args){
  List<Persona> persone = new ArrayList<Persona>();
  
  //popoliamo la nostra lista
  persone.add(new Persona(27, "marco", "bianco"));
  persone.add(new Persona(80, "luca", "arancio"));
  persone.add(new Persona(75, "giovanni", "rossi"));
  persone.add(new Persona(29, "mario", "bianchi"));
  
  System.out.println("-- LISTA NON ORDINATA --");
  print(persone);
  
  /**
  * ordiniamo la lista utilizzando il metodo sort della classe Collections
  * tale metodo riceve una List di oggetti che devono implementare il metodo
  * Comparable. Nel nostro caso la lista viene ordinata per età
  */

  System.out.println("-- ORDINE PER ETA' --");
  Collections.sort(persone);
  print(persone);
  
  /**
  * ordiniamo la lista utilizzando il metodo sort della classe Collections
  * tale metodo riceve una List di oggetti ed un istanza di un oggetto Comparator
  * Nel nostro caso la lista viene ordinata per cognome
  */

  System.out.println("-- ORDINE PER COGNOMI --");
  Collections.sort(persone, new CognomeComparator());
  print(persone);
}

//tale metodo permette di stampare la lista
private static void print(Collection<Persona> c){
  Iterator<Persona> it = c.iterator();

  while (it.hasNext()){
    Persona p = it.next();
    System.out.println(p.getNome() + " " + p.getCognome() + " " + p.getEta());
  }
}

Abbiamo utilizzato due metodi differenti per ordinare la lista:

  1. Collections.sort(persone) che riceve in input una List di oggetti che implementano l'interfaccia Comparable. Nel nostro esempio gli elementi vengono ordinati in base all'età;
  2. Collections.sort(persone, new CognomeComparator()) che riceve in input una List di oggetti e un'istanza di una classe che implementa l'interfaccia Comparator. Nel nostro esempio gli elementi vengono ordinati in base al cognome. Naturalmente è possibile creare diversi Comparator che permettono di ordinare la lista in altrettanti modi.

Ti consigliamo anche