Collezioni

Progettazione e Sviluppo del Software

C.D.L. Tecnologie dei Sistemi Informatici

Gianluca Aguzzi — gianluca.aguzzi@unibo.it

Angelo Filaseta — angelo.filaseta@unibo.it

versione stampabile

Riconoscimenti

  • Questo materiale è ampiamente basato su quello realizzato dai Prof. Mirko Viroli e Roberto Casadei, che ringrazio.

  • Ogni errore riscontratovi è esclusiva responsabilità degli autori di questo documento.

Outline

Goal della lezione

  • Illustrare la struttura del Java Collections Framework
  • Mostrare gli utilizzi delle funzonalità base
  • Discutere alcune tecniche di programmazione correlate

Argomenti

  • Presentazione Java Collections Framework
  • Iteratori e foreach
  • Collezioni, Set, e Liste
  • Funzioni di utilità in Arrays e Collections
  • Implementazioni di Set, List e Map

Java Collections Framework (JCF)

Java Collections Framework

Java Collections Framework (JCF)

  • È una libreria del linguaggio Java
  • È una parte del package java.util
  • Gestisce strutture dati (o collezioni) e relativi algoritmi

Importanza pratica

  • Virtualmente ogni sistema fa uso di collezioni di oggetti
  • Conoscerne struttura e dettagli vi farà programmatori migliori

Importanza didattica

  • Fornisce ottimi esempi di uso di composizione, ereditarietà, genericità
  • Mette in pratica pattern di programmazione di interesse
  • Impatta su alcuni aspetti del linguaggio da approfondire

JCF – struttura semplificata

Produces
keys
values
Iterable
iterator() : Iterator<E>
Iterator
next() : E
hasNext() : boolean
Map
keySet() : Set<K>
values() : Collection<V>
get(K key) : V
put(K key, V value) : boolean
Collection
add(E element) : boolean
remove(E element) : boolean
contains(E element) : boolean
List
get(int index) : : E
Set<E>
ArrayList<E>
LinkedList<E>
HashSet<E>
TreeSet<E>
LinkedHashSet<E>
TreeMap<K, V>
HashMap<K, V>
LinkedHashMap<K, V>

JCF – alcuni aspetti generali

È complessivamente piuttosto articolato

  • Un nostro obbiettivo è quello di isolare una sua sottoparte di interesse
  • Identificando e motivando le funzionalità prodotte

Due categorie di collezioni, ognuna con varie incarnazioni

  • Collection – contenitore di elementi atomici; 2 sottotipi di collezioni
    1. List (sequenze)
    2. Set (no duplicazioni)
  • Map – contenitore di coppie chiave-valore (memoria associativa)

Interfacce/classi di interesse:

  • Interfacce: Collection, List, Set, Map, Iterator, Comparable
  • Classi collection: ArrayList, LinkedList, HashSet, TreeSet, HashMap
  • Classi con funzionalità: Collections, Arrays

Una nota su eccezioni e JCF

Eccezioni: un argomento che tratteremo in dettaglio

Un meccanismo usato per gestire eventi ritenuti fuori dalla normale esecuzione (errori), ossia per dichiararli, lanciarli, intercettarli

JCF e eccezioni

  • Ogni collection ha sue regole di funzionamento, e non ammette certe operazioni che richiedono controlli a tempo di esecuzione
    • ad esempio, certe collezioni sono immutabili, e non si può tentare di scriverci
    • oppure, non si può ottenere un elemento da una collezione vuota
  • Molti metodi dichiarano che possono lanciare eccezioni – ma possiamo non preoccuparcene per ora

Iteratori e foreach

Foreach

Costrutto foreach

  • Abbiamo visto che può essere usato per iterare su un array in modo più astratto (compatto, leggibile)
for(int i: array) { ... }
  • Java fornisce anche un meccanismo per usare il foreach su qualunque collection, in particolare, su qualunque oggetto che implementa l’interfaccia java.lang.Iterable<X>

Iterable e Iterator

  • L’interfaccia Iterable (contratto degli oggetti iterabili) ha un metodo per generare e restituire un (nuovo) Iterator
  • Un iteratore è un oggetto con metodi next(), hasNext() (e remove())
  • Dato l’oggetto coll che implementa Iterable<T> allora il foreach diventa:
for(final T element: coll) { ... }

Interfacce per l’iterazione

package java.lang;
import java.util.Iterator;
public interface Iterable<T> {
    /**
     * Returns an iterator over a set of elements of type T.
     *
     * @return an Iterator.
     */
    Iterator<T> iterator();
}
package java.util;

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove(); // throws UnsupportedOperationException
}
package java.util;

public interface Collection<E> implements Iterable<E> { .. }

Interfacce per l’iterazione – UML

Iteratori e collezioni: preview

import java.util.*;

public class UseCollectionIterator {
	public static void main(String[] s) {
		// Uso la LinkedList
		final List<Double> list = new LinkedList<>();
		// Inserisco 50 elementi
		for (int i = 0; i < 50; i++) {
			list.add(Math.random());
		}
		// Stampo con un foreach
		int count = 0;
		for (final double d: list) {
			System.out.println(count++ + "\t" + d);
		}
		// 0 0.10230513602737423
		// 1 0.4318582138894327
		// 2 0.5239222319032795
		// ..
	}
}

Collection, List, Set

Interfaccia Collection<E> (implementa Iterable<E>)

Ruolo di questo tipo di dato

  • È la radice della gerarchia delle collezioni
  • Rappresenta gruppi di oggetti (duplicati/non, ordinati/non)
  • Implementata via sottointerfacce (List e Set)

Assunzioni

  • Definisce operazioni base valide per tutte le collezioni
  • Assume implicitamente che ogni collezione abbia due costruttori
    • Senza argomenti, che genera una collezione vuota
    • Che accetta una Collection, dal quale prende tutti gli elementi
  • Le operazioni di modifica sono tutte “opzionali”
    • potrebbero lanciare un UnsupportedOperationException
  • Tutte le operazioni di ricerca (e.g. c.contains(o)) lavorano sulla base del metodo Object.equals() da chiamare sugli elementi
    • questo metodo accetta un Object, influendo su alcuni metodi di Collection

Collection: estratto dell’interfaccia

public interface Collection<E> extends Iterable<E> {

    // Query Operations
    int size();	                // number of elements
    boolean isEmpty();          // is the size zero?
    boolean contains(Object o); // does it contain an element equal to o?
    Iterator<E> iterator();     // yields an iterator
    Object[] toArray();	        // convert to array of objects
    <T> T[] toArray(T[] a);     // puts in `a`, or create new if too small

    // Modification Operations
    boolean add(E e);           // adds e
    boolean remove(Object o);   // remove one element that is equal to o

    // Bulk Operations
    boolean containsAll(Collection<?> c);       // contain all elements in c?
    boolean addAll(Collection<? extends E> c);  // add all elements in c
    boolean removeAll(Collection<?> c);         // remove all elements in c
    boolean retainAll(Collection<?> c);         // keep only elements in c
    void clear();                               // remove all element
    
    // ...and other methods introduced in Java 8
}

Usare le collezioni

  • Nota: invocazione metodi dell’interfaccia Collection
  • Interazione con array via ad es. Arrays.asList() e Collection.toArray()
public class UseCollection {
    public static void main(String[] s) {
        // Uso una incarnazione, ma poi lavoro sull'interfaccia
        final Collection<Integer> coll = new ArrayList<>();
        coll.addAll(Arrays.asList(1, 3, 5, 7, 9, 11)); // var-args
        System.out.println(coll); // [1, 3, 5, 7, 9, 11]

        coll.add(13);
        coll.add(15);
        coll.add(15);
        coll.remove(7);
        System.out.println(coll); // [1, 3, 5, 9, 11, 13, 15, 15]

        coll.removeAll(Arrays.asList(11, 13, 15));
        coll.retainAll(Arrays.asList(1, 2, 3, 4, 5));
        System.out.println(coll); // [1, 3, 5]
        System.out.println(coll.contains(3)); // true
        System.out.println(Arrays.toString(coll.toArray()));

        var a = new Integer[2];
        a = coll.toArray(a);
        System.out.println(Arrays.deepToString(a));
    }
}

Creare collezioni (immutabili) – Java 9+

  • Metodi statici factory .of(...) e .copyOf(c) su List, Set, …
    • Nota: Arrays.ofList visto precedentemente, invece, crea una lista mutabile (e consente valori null)
public class UseFactories {
	public static void main(String[] s) {
		// Metodi statici di creazione per Set e List immutabili

		final Set<Integer> set = Set.of(1, 2, 3, 4, 5, 6);
		System.out.println(set);

		final List<String> list = List.of("a", "b", "c", "a");
		System.out.println(list);

		final Set<String> set2 = Set.copyOf(list);
		System.out.println(set2);

		System.out.println(list.hashCode());
	}
}

Set e List: introduzione

Set

  • Rappresenta collezioni senza duplicati
    • nessuna coppia di elementi porta Object.equals() a dare true
    • non vi sono due elementi null
    • I metodi di modifica devono rispettare la non duplicazione
  • Non aggiunge metodi rispetto a Collection

List

  • Rappresenta sequenze di elementi
  • Rispetto alle collezioni generiche:
    • Ha metodi per accedere ad un elemento per posizione (0-based)

La scelta fra queste due tipologie non dipende da motivi di performance, ma da quale modello di collezione serva!

Set e List: interfacce

public interface Set<E> extends Collection<E> { }
public interface List<E> extends Collection<E> {
    // Additional Bulk Operations 
    boolean addAll(int index, Collection<? extends E> c);

    // Positional Access Operations
    E get(int index);               // get at position index
    E set(int index, E element);    // set into position index
    void add(int index, E element); // add, shifting others
    E remove(int index);            // remove at position index

    // Search Operations
    int indexOf(Object o);          // first equals to o
    int lastIndexOf(Object o);      // last equals to o

    // List Iterators (enable traversal in both directions, modifications etc.)
    ListIterator<E> listIterator();           // iterator from 0
    ListIterator<E> listIterator(int index);  // ..from index

    // View
    List<E> subList(int fromIndex, int toIndex);
}

Uso di collezioni: linee guida generali

  • In variabili, argomenti, tipi di ritorno, si usano le interfacce
void m(List<Integer> lst) {
    final Set<String> names = // Dichiaro Set, non HashSet o altra implementazione
}
  • Le classi concrete solo in fase di istanziazione, nella new, a parte casi molto particolari
final Set<String> names = new HashSet<>();

Implementazioni di Set

Implementazioni di Set

Caratteristiche dei set

  • Nessun elemento duplicato (nel senso di Object.equals())
  • Veloce nella verifica della presenza di elementi
    • Mentre in un array bisogna verificare elemento-per-elemento (ricerca sequenziale), i Set possono avere prestazioni migliori
    • Si usa il metodo Object.hashCode() come funzione di hash, usata per posizionare gli elementi in uno store di elevate dimensioni

Implementazione 1: LinkedHashSet e HashSet

  • Non ordinati
  • La versione Linked ha ordine di iterazione predicibile (quello degli elementi inseriti)

Implementazione 2: TreeSet

  • Gli elementi sono ordinati
  • Richiede che gli elementi siano Comparable, oppure che venga fornito un Comparator

(Linked)HashSet

Idea di base: tecnica di hashing (via Object.hashCode())

  • Si crea un array di elementi più grande del necessario (p.e. almeno il 25% in più), di dimensione size
  • Aggiunta di un elemento e
    • lo si inserisce in posizione e.hashCode() % size
    • se la posizione è già occupata, lo si inserisce nella prima disponibile
    • se l’array si riempie va espanso e si deve fare il rehashing
  • Ricerca di un elemento f
    • si guarda a partire da f.hashCode() % size, usando Object.equals()
    • La funzione di hashing deve evitare il più possibile le collisioni
  • Risultato: scritture/letture sono $O(1)$ ammortizzato

Dettagli interni

  • Realizzata tramite HashMap, che approfondiremo in futuro

Costruttori di HashSet

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {

    // Set vuoto, usa hashmap con capacità 16
    public HashSet() {...}
    
    // Set con elementi di c, usa hashmap del 25% più grande di c
    public HashSet(Collection<? extends E> c) {...}

    // Set vuoto
    public HashSet(int initialCapacity, float loadFactor) {...}

    // Set vuoto, loadFactor = 0.75
    public HashSet(int initialCapacity) {...}
    
    /* Gli altri metodi di Collection seguono... */
}

equals() e hashCode()

La loro corretta implementazione è cruciale

  • Le classi di libreria di Java sono già OK
  • Object uguaglia lo stesso oggetto e l’hashing restituisce la posizione in memoria..
  • .. quindi nuove classi devono ridefinire equals() e hashCode() opportunamente

Quale funzione di hashing?

  • oggetti equals devono avere lo stesso hashCode
  • non è detto il viceversa, ma è opportuno per avere buone performance di HashSet
  • è possibile implementare un buon hashCode() sfruttando il metodo di libreria Objects.hash(Object...)
    • andranno passati come argomenti tutti i campi che vengono controllati nella equals
    • ne esistono di migliori, per applicazioni che hanno bisogno di alte performance: (djb2, murmur3)

TreeSet<E>

  • Assume che esista un ordine totale fra gli elementi
  • Quindi ogni elemento ha una sua posizione nell’elenco
  • Questo consente l’approccio dicotomico alla ricerca

Realizzazione ordinamento: due approcci (interno o esterno)

  • O con elementi che implementano direttamente Comparable
    • Nota che, p.e., Integer implementa Comparable<Integer>
  • O attraverso un Comparator esterno fornito alla new

Implementazione TreeSet

  • Basata su red-black tree (albero binario bilanciato)
  • Tempo logaritmico per inserimento, cancellazione, e ricerca

Comparazione “interna” agli elementi

public interface Comparable<T> {
    /*
     * returns:
     * 0 if this is equal to other
     * positive int (1 is preferred) if this > other
     * negative int (-1 is preferred) if this < o
     */
    public int compareTo(T other);
}

Consente di definire una nozione di ordine naturale sugli oggetti di una classe

Ordine naturale negli oggetti Java

Pre-implementato in molte classi di libreria (più di 100!)

class Integer extends Number implements Comparable<Integer> { ... }
class String extends Object implements Comparable<String>, ... { ... }

Implementabile in modo personalizzato nelle nostre classi

public class Person implements Comparable<Person> {
    private final int birthYear;
    private final String surname;
    /*
     * Sort by year, and if they are equal by name
     */
    public int compareTo(final Person other) {
        final var byYear = this.birthYear > other.birthYear
            ? 1
            : (this.birthYear < other.birthYear ? -1 : 0)
        // Alternatively, just:
        // final var byYear = Integer.compare(this.birthYear, other.birthYear)
        return byYear == 0 ? this.name.compareTo(other.name) : byYear;
    }
}

Comparazione interna

// String implements Comparable<String>
final var internal = new TreeSet<String>();
internal.add("zzz");
internal.add("lll");
internal.add("aaaaa");
internal.add("10");
internal.add("2");
// String uses lexicographical ordering!
System.out.println(internal); // [10, 2, aaaaa, lll, zzz]

Se a una collezione ordinata non è fornito un Comparator esterno specifico, allora sfrutterà l’ordinamento naturale del tipo degli elementi (quindi se il tipo T degli elementi non implementa Comparable<? super T>, un’eccezione verrà sollevata)

Comparazione esterna

Va passato un comparatore personalizzato (una classe che implementa Comparator), due casi d’uso:

  • Dobbiamo ordinare una collezione di elementi che non implementano Comparable
  • Vogliamo ordinare in modo differente dall’ordine naturale
public class StringByLength implements Comparator<String> {
    // Sort by length, then lexicographically
    public int compare(final String s1, final String s2) {
        final var byLength = Integer.compare(s1.length(), s2.length());
        return byLength == 0 ? s1.compareTo(s2) : byLength;
    }
}
final var external = new TreeSet<String>(new StringByLength());
external.add("zzz");
external.add("lll");
external.add("aaaaa");
external.add("10");
external.add("2");
// Custom ordering!
System.out.println(external); // [2, 10, lll, zzz, aaaaa]

Perché il tipo Comparator<? super E> (Approfondimento)

Data una classe SortedSet<E> il suo comparatore ha tipo Comparator<? super E>, perché non semplicemente Comparator<E>?

È corretto

  • Comparator ha metodi che hanno E solo come argomento
  • quindi l’uso di Comparator<? super E> è una generalizzazione di Comparator<E>

È utile

  • Supponiamo di aver costruito un comparatore per SimpleLamp, e che questo sia usabile anche per tutte le specializzazioni successivamente costruite (è la situazione tipica)
  • Anche un SortedSet<UnlimitedLamp> deve poter usare il Comparator<SimpleLamp>, ma questo è possibile solo grazie al suo tipo atteso Comparator<? super E>

Implementazioni di List

List

public interface List<E> extends Collection<E> {
    // Additional Bulk Operations 
    // aggiunge gli elementi in pos. index
    boolean addAll(int index, Collection<? extends E> c); 

    // Positional Access Operations
    E get(int index);
    E set(int index, E element);
    void add(int index, E element);
    E remove(int index);

    // Search Operations
    int indexOf(Object o);	// basato su Object.equals
    int lastIndexOf(Object o);  // basato su Object.equals

    // List Iterators
    ListIterator<E> listIterator();
    ListIterator<E> listIterator(int index);

    // View
    List<E> subList(int fromIndex, int toIndex);
}

Implementazioni di List

Caratteristiche delle liste

  • Sono sequenze: ogni elemento ha una posizione
  • $\rightarrow$ Il problema fondamentale è realizzare i metodi posizionali in modo efficiente, considerando il fatto che la lista può modificare le proprie dimensioni nel tempo (altrimenti andrebbe bene un array)

Approccio 1: ArrayList

  • Internamente usa un array di elementi con capacità maggiore di quella al momento necessaria.
  • Se serve ulteriore spazio si alloca trasparentemente un nuovo e più grande array

Approccio 2: LinkedList

ArrayList

Caratteristiche di performance

  • Lettura/scrittura in data posizione sono a tempo costante
  • La add() è tempo costante ammortizzato, ossia, $n$ add si effettuano in $O(n)$
  • Tutte le altre operazioni sono a tempo lineare

Funzionalità aggiuntive

Per migliorare le performance (e l’occupazione in memoria) in taluni casi l’utente esperto può usare funzioni aggiuntive

  • Specificare la dimensione iniziale dell’array interno nella new
  • trimToSize() e ensureCapacity() per modifiche in itinere

LinkedList

lista doppiamente linkata (che può essere traversata dall’inizio o dalla fine)

Caratteristiche di performance

  • Accesso e modifica in una data posizione hanno costo lineare
  • Operazioni in testa o coda, quindi, sono a tempo costante
  • Usa in generale meglio la memoria rispetto ArrayList
  • (di norma però si preferisce ArrayList)

Funzionalità aggiuntive

  • Implementa anche l’interfaccia Deque, usata per rappresentare una coda bidirezionale (double-ended queue), potenzialmente con dimensione limitata

LinkedList: funzioni aggiuntive relative a code (e stack)

public interface Queue<E> extends Collection<E> {
    boolean offer(E e); // inserisce se c'è spazio
    E poll();		// preleva se c'è il primo
    E element();        // legge se c'è il primo, altrimenti eccezione
    E peek();		// legge se c'è il primo, altrimenti null
}
// a "deque" (pronounced "deck") is a "double-ended queue"
public interface Deque<E> extends Queue<E> {
    void addFirst(E e);
    void addLast(E e);
    boolean offerFirst(E e);
    boolean offerLast(E e);
    E removeFirst();
    E removeLast();
    E pollFirst();
    E pollLast();
    E getFirst();
    E getLast();
    E peekFirst();
    E peekLast();
    boolean removeFirstOccurrence(Object o);
    boolean removeLastOccurrence(Object o);
    void push(E e); // identical to addFirst()
    E pop();        // identical to removeFirst()
}

LinkedList: costruzione

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable {

    public LinkedList() {...}

    public LinkedList(Collection<? extends E> c) {...}
}

UseLinkedList

public class UseLinkedList {
	private static final String ELEMS = "A B C D E F G H I L M";

	public static void main(String[] s) {
		final LinkedList<String> llist = new LinkedList<>(Arrays.asList(ELEMS.split(" ")));
		llist.addFirst("*");
		llist.addLast("*");
		llist.removeFirstOccurrence("*");
		llist.removeLastOccurrence("*");

		// bad performance
		llist.add(llist.indexOf("L"), "K");
		// better performance
		final ListIterator<String> it = llist.listIterator();
		while (it.hasNext()) {
			if (it.next().equals("I")) {
				it.add("J");
				break;
			}
		}
		final String[] str = llist.toArray(new String[0]);
		System.out.println(Arrays.toString(str));
	}
}

Altre classi di utilità: Arrays e Collections

Classi di utilità (moduli): Arrays e Collections

java.util.Arrays

  • Contiene varie funzionalità d’ausilio alla gestione degli array
  • In genere ha varie versione dei metodi per ogni array di tipo primitivo
  • Ricerca (binarySearch()), ordinamento (quicksort, sort()), copia (copyOf()), riempimento (fill())
  • Operazioni base (toString, equals, hashCode), anche ricorsive

java.util.Collections

  • Raccoglie metodi statici che sarebbero potuti appartenere alle varie classi/interfacce viste
  • Ricerca (binaria/dicotomica), ordinamento (quicksort), copia, fill, frequency, min, max, sublist, replace, reverse, rotate, shuffle
  • Con esempi notevoli d’uso delle wilcard

Arrays: qualche esempio di metodi


public class Arrays {
    public static void sort(Object[] a, int fromIndex, int toIndex) {...}
    public static <T> void sort(T[] a, Comparator<? super T> c) {...}
    ...
    public static int binarySearch(int[] a, int key) {...}
    public static int binarySearch(char[] a, char key) {...}
    public static <T> int binarySearch(T[] a, T key, Comparator<? super T> c) {...}
    ...
    public static <T> List<T> asList(T... a) {...}
    public static <T> T[] copyOfRange(T[] original, int from, int to) {...}
    public static void fill(Object[] a, int from, int to, Object val) {...}
    	
    public static boolean deepEquals(Object[] a1, Object[] a2) {...}
    public static int deepHashCode(Object a[]) {...}
    public static String deepToString(Object[] a) {...}
    
    public static String toString(long[] a) {...}
    public static String toString(int[] a) {...}
    public static String toString(Object[] a) {...}

}

UseArrays: qualche esempio di applicazione

public class UseArrays implements Comparator<Integer> {
    public int compare(Integer a, Integer b) {
        return b.compareTo(a); // ordine inverso
    }

    public static void main(String[] s) {
        String[] array = new String[] { "a", "b", "c", "d", "e", "f" };
        List<String> list = Arrays.asList("a", "b", "c", "d", "e", "f");

        final Integer[] a = new Integer[20];
        for (int i = 0; i < 20; i++) {
            a[i] = (int) (Math.random() * 100);
        }
        System.out.println("rand: " + Arrays.toString(a));
        Arrays.sort(a); // sort in ordine naturale
        System.out.println("sort1: " + Arrays.toString(a));
        Arrays.sort(a, new UseArrays()); // sort col comparator
        System.out.println("sort2: " + Arrays.toString(a));
        Arrays.fill(a, 10, 15, 0); // fill nel range
        System.out.println("fill: " + Arrays.toString(a));

        final Integer[][] b = new Integer[10][];
        Arrays.fill(b, a); // fill di un array di array
        System.out.println("recu: " + Arrays.deepToString(b));
    }
}

Collections: qualche esempio di metodi

// nota: tutti metodi public static!!
public class Collections { 
    // ordinamenti e varie
    <T extends Comparable<? super T>> void sort(List<T> list) {...}
    <T> void sort(List<T> list, Comparator<? super T> c) {...}
    <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)
    <T> T min(Collection<? extends T> coll, Comparator<? super T> comp)
    
    // modifiche
    void reverse(List<?> list) {...}
    void shuffle(List<?> list) {...}
    <T> void fill(List<? super T> list, T obj) {...}
    <T> void copy(List<? super T> dest, List<? extends T> src) {...}
    
    // letture varie
    int indexOfSubList(List<?> source, List<?> target) {...}
    boolean disjoint(Collection<?> c1, Collection<?> c2) {...}
    int frequency(Collection<?> c, Object o) {...}
    
    // costruzioni di collezioni
    <T> List<T> emptyList() {...} 
    <T> Set<T> emptySet() {...} 
    <T> List<T> nCopies(int n, T o) {...}
    <T> Set<T> singleton(T o) {...}
    <T> List<T> singletonList(T o) {...}
}

UseCollections: qualche esempio di applicazione

public class UseCollections {
    public static void main(String[] s) {
        final List<Integer> list = Arrays.asList(new Integer[] { 0, 1, 2, 3, 4 });
        final Set<List<Integer>> set = new HashSet<>();

        for (int i = 0; i < 5; i++) {
            final List<Integer> l2 = new ArrayList<>(list);
            Collections.shuffle(l2);// permuto
            if (!set.contains(l2)) { // no duplicazione!
                set.add(l2); // aggiungo
            }
        }
        System.out.println("shuf: " + set); // [[4,1,2,3,0],[3,1,4,0,2],..

        int ct = 0;
        for (final List<Integer> l : set) {
            Collections.fill(l, ct++);
        }
        System.out.println("inc: " + set); // [[0,0,0,0,0],[1,1,1,1,1],..

        System.out.println("cop: " + Collections.nCopies(5, list));
        // [[0,1,2,3,4],[0,1,2,3,4],..
    }
}

java.util.Map

mappe associative (o dizionari)

Map

  • Una mappa (anche detta mappa associativa o dizionario) è una corrispondenza tra chiavi e valori, dove le chiavi (e quindi le coppie) formano un set (non ci sono chiavi duplicate)
public interface Map<K,V> {

    // Query Operations
    int size();
    boolean isEmpty();
    boolean containsKey(Object key);        // usa Object.equals
    boolean containsValue(Object value);    // usa Object.equals
    V get(Object key);                      // accesso a valore

    // Modification Operations
    V put(K key, V value);          // inserimento chiave-valore
    V remove(Object key);           // rimozione chiave(-valore)

    // Bulk Operations
    void putAll(Map<? extends K, ? extends V> m);
    void clear();                   // cancella tutti

    // Views
    Set<K> keySet();                    // set di valori
    Collection<V> values();             // collezione di chiavi
    
    ... // qualche altro
}

Implementazioni notevoli di Map

Map<K,V>

  • Rappresenta una funzione dal dominio K in V
  • La mappa tiene tutte le associazioni (o “entry”)
  • Non posso esistere due entry con stessa chiave (Object.equals)

HashMap

  • Sostanzialmente un HashSet di coppie Key, Value
  • L’accesso ad un valore tramite la chiave è fatto con hashing
  • Accesso a tempo costante, a discapito di overhead in memoria

LinkedHashMap

  • Come sopra, ma l’ordine di iterazione è predicibile (è l’ordine di inserimento)

TreeMap

  • Sostanzialmente un TreeSet di coppie Key, Value
  • L’accesso ad un valore tramite la chiave è fatto con red-black tree
  • Accesso in tempo logaritmico
  • Le chiavi devono essere ordinate, come per i TreeSet

Usare le mappe

public class UseMap {
	public static void main(String[] args) {
		// Uso una incarnazione, ma poi lavoro sull'interfaccia
		final Map<Integer, String> map = new HashMap<>();
		// Una mappa è una funzione discreta
		map.put(345211, "Bianchi");
		map.put(345122, "Rossi");
		map.put(243001, "Verdi");
		System.out.println(map); // {345211=Bianchi, 243001=Verdi, 345122=Rossi}
		map.put(243001, "Neri"); // Rimpiazza Verdi
		final Map<String,Integer> map2 = Map.of("foo", 5, "bar", 7);

		// modo prestante per accedere alle coppie
		for(final Map.Entry<String,Integer> kv : map2.entrySet()) {
			System.out.println("Chiave: " + kv.getKey() + " Valore: " + kv.getValue());
		}
		// modo poco prestante per accedere alle coppie chiave-valore
		for (final Integer i : map.keySet()) {
			System.out.println("Chiave: " + i + " Valore: " + map.get(i));
		}
		// modo prestante per accedere ai soli valori
		for (final String s : map.values()) {
			System.out.println("Valore: " + s);
		}
	}
}