Java Collection Framework (JCF) è una parte della libreria standard dedicata alle collezioni, intese come classi deputate a contenere altri oggetti.
Questa libreria offre strutture dati di supporto, molto utili alla programmazione, come liste, array di dimensione dinamica, insiemi, mappe associative (anche chiamate dizionari) e code.
Le classi e interfacce del JCF si dividono in due gerarchie:
Inoltre, la classe Collections (si noti la “s” finale) contiene numerosi algoritmi di supporto, ad esempio, metodi che effettuano l’ordinamento.
L’interfaccia Collection estende la versione parametrica di Iterable, che andiamo ad introdurre.
Ora che abbiamo introdotto la programmazione parametrica, possiamo svelare che le interfacce Iterator e Iterable sono, in realtà, parametriche.
La loro definizione è la seguente:
public interface Iterator {
public E next();
public boolean hasNext();
public void remove();
}
public interface Iterable {
public Iterator iterator();
}
Lo scopo ultimo del parametro di tipo consiste nel permettere al metodo next di restituire un oggetto del tipo appropriato, evitando che il chiamante debba ricorrere ad un cast.
Java 1.5 ha introdotto anche un nuovo tipo di ciclo for, chiamato ciclo “for each”.
Cominciamo con un esempio:
String[] array = {"uno", "due", "tre"};
for (String s: array)
System.out.println(s);
Il ciclo di sopra stampa tutte le stringhe contenute nell’array, una per rigo.
Questa nuova forma di ciclo permette di iterare su un array senza dover esplicitamente utilizzare un indice, quindi senza il rischio di sbagliare gli estremi dell’iterazione.
Oltre che per gli array, il ciclo for-each funziona anche su tutti gli oggetti che implementano Iterable<E>, come illustrato nella prossima slide.
Se un oggetto x appartiene ad una classe che implementa Iterable< A >, per una data classe A, è possibile scrivere il seguente ciclo:
for (A a: x) {
// corpo del ciclo
...
}
Il ciclo di sopra è equivalente al blocco seguente:
Iterator< A > it = x.iterator();
while (it.hasNext()) {
A a = it.next();
// corpo del ciclo
...
}
È evidente che la nuova forma di ciclo semplifica molto la vita al programmatore, allo stesso tempo riducendo drasticamente il rischio di scrivere codice errato.
Definire una classe Primes che rappresenta l’insieme dei numeri primi.
Il campo statico “all” fornisce un oggetto su cui si può iterare, ottenendo l’elenco di tutti i numeri primi.
Non deve essere possibile creare oggetti di tipo Primes.
L’implementazione deve rispettare il seguente esempio d’uso:
for (Integer i: Primes.all) {
if (i > 20) break;
System.out.println(i);
}
Output dell’esempio d’uso:
1
3
5
7
11
13
17
19
Prima di presentare una soluzione, discutiamo brevemente della sua progettazione.
La traccia richiede che “all” sia un campo statico.
È naturale che sia anche “public”, per essere visto dall’esterno, e “final”, in modo che non possa essere modificato.
Inoltre, in base al caso d’uso, deve puntare ad un oggetto che implementi Iterable<Integer>.
Per quanto riguarda la richiesta che la classe Primes non sia instanziabile, si può ottenere questo risultato dichiarandola astratta, oppure dotandola di un solo costruttore, privato.
La prossima slide presenta una possibile implementazione.
Si noti l’utilizzo di due classi anonime annidate, una per implementare Iterable e l’altra per implementare Iterator.
Il diagramma in figura illustra le interfacce direttamente collegate con Collection.
Abbiamo già detto che Collection estende Iterable, e quindi fornisce un metodo che restituisce un iteratore.
La prossima slide illustra gli altri metodi elencati nella figura.
Collection viene estesa dalle tre interfacce parametriche Set, List e Queue.
Come dice il nome, una collection rappresenta un insieme di oggetti. L’interfaccia ha un parametro di tipo che indica il tipo degli oggetti contenuti. I metodi principali dell’interfaccia sono i seguenti:
Come si vede, le collezioni fanno affidamento al metodo equals per identificare gli elementi, le implementazioni concrete di Collection usano anche altri metodi, come vedremo tra breve.
Per quanto riguarda il metodo add, il motivo per cui restituisce un valore booleano diventerà chiaro quando esamineremo alcune implementazioni concrete di Collection
Può sorprendere che i metodi contains e remove accettino Object invece del tipo parametrico E:
L’interfaccia Set non aggiunge metodi a Collection. Tuttavia, restringe, ovvero rende più specifici, i contratti di alcuni metodi, come add.
La specificità di un Set, rispetto ad una Collection generica, è che un Set non può contenere elementi duplicati.
Quindi, se si tenta di aggiungere con add un elemento che è già presente (ovvero, un oggetto che risulta uguale, secondo equals, ad uno già presente), la collezione non viene modificata e add restituisce false.
L’interfaccia SortedSet, che estende Set, rappresenta un insieme sui cui elementi è definita una relazione d’ordine (totale).
L’iteratore di un SortedSet garantisce che gli elementi saranno visitati in ordine, dal più piccolo al più grande.
Inoltre, un tale insieme dispone di due metodi extra:
TreeSet è un Set implementato internamente come albero di ricerca bilanciato.
Gli elementi devono essere dotati di una relazione d’ordine, in uno dei seguenti modi:
oppure
Le operazioni principali di TreeSet hanno la seguente complessità di tempo, tipica degli alberi di ricerca bilanciati:
size O(1)
isEmpty O(1)
add O(log n)
contains O(log n)
remove O(log n)
TreeSet utilizza un ordinamento, fornito da Comparable o da Comparator, per smistare e poi ritrovare gli elementi all’interno dell’albero.
Allo stesso tempo, come previsto dall’interfaccia Set, viene usato il metodo equals per identificare gli elementi.
Se l’ordinamento non è coerente con l’uguaglianza definita da equals, la struttura dati può comportarsi in modo imprevedibile.
Consideriamo il caso di Comparable.
Per ogni coppia di elementi x e y, in aggiunta alle normali proprietà di equals e compareTo, deve valere la seguente condizione:
x.equals(y) è vero se e solo se x.compareTo(y) == 0
Una condizione analoga deve valere nel caso venga fornito un oggetto di tipo Comparator.
4. Risoluzione dell'overloading e dell'overriding
5. Controllo di uguaglianza tra oggetti
6. Classi interne, locali ed anonime
7. Iteratori, teoria e pratica
8. Clonazione di oggetti. Confronto tra oggetti.
9. Elementi di programmazione di interfacce grafiche
10. Il paradigma Model-View-Controller. Il pattern Strategy
11. I pattern Composite e Decorator
12. I pattern Template Method e Factory Method
13. Classi e metodi parametrici
14. La libreria Java Collection Framework: le interfacce Iterable, ...
15. La libreria Java Collection Framework: la classe HashSet e le l...
16. Parametri di tipo con limiti
17. L'implementazione della programmazione generica: la cancellazio...
18. La riflessione
19. Introduzione al multi-threading
22. Classi enumerate