Collezioni

Progettazione e Sviluppo del Software

C.D.L. Tecnologie dei Sistemi Informatici

Danilo Pianini — danilo.pianini@unibo.it

Gianluca Aguzzi — gianluca.aguzzi@unibo.it

Angelo Filaseta — angelo.filaseta@unibo.it

Compiled on: 2025-12-05 — versione stampabile

back

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 ed 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 (final var 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()), che può quindi iterare su una collezione una sola volta
    • È “monouso”: una volta terminata l’iterazione, non si può più riutilizzare lo stesso iteratore
    • Ne va creato uno nuovo, chiedendolo all’Iterable
  • Dato l’oggetto collection che implementa Iterable<T> allora il foreach diventa:
for (final T element: collection) { ... }
// o, equivalentemente:
for (final var element: collection) { ... }

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> { .. }

Iteratori e collezioni: preview

import java.util.*;

public class UseCollectionIterator {
    static void main() {
        // 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 + " " + d);
        }
        // 1 0.10230513602737423
        // 2 0.4318582138894327
        // 3 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

  • Interazione fra collezioni e array tramite metodi:
    • Arrays.asList(), metodo statico che converte un array in una List
    • Collection.toArray(), metodo istanza che converte una collezione in un array
package it.unibo.collections.collection;

import java.util.*;

public class UseCollection {
    static void main() {
        // Uso ArrayList come implementazione, ma dichiaro e uso l'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); // Invocazione di metodi dell'interfaccia Collection
        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()));
        final var a = coll.toArray();
        System.out.println(Arrays.deepToString(a));
    }
}

Creare collezioni (immutabili) – Java 9+

  • Metodi statici factory .of(...) e .copyOf(c) su List, Set, …
    • Nota: Arrays.asList 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 var set2 = Set.copyOf(list);
        System.out.println(set2);
        // set2.add("d"); // UnsupportedOperationException
        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 ordinate di elementi
  • Rispetto alle collezioni generiche:
    • Ha metodi per accedere a un elemento per posizione (0-indicizzata)

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(final List<Integer> lst) {
    // Dichiaro Set, non HashSet o altra implementazione, e istanzio una sua implementazione
    final Set<String> names = new LinkedHashSet<>();
}
  • Le classi concrete solo in fase di istanziazione, nella new, a parte casi molto particolari

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 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>
    • Detto anche ordine naturale
  • 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 contravariante di Comparator<E>
    • Provate a leggerla alla Kotlin/C#: Comparator<in E>:
      • $\Rightarrow$ Comparator che accetta oggetti di tipo E in input
      • $\Rightarrow$ Comparator che non restituisce mai oggetti di tipo E

È utile

  • Supponiamo di aver costruito un comparatore per Person, e che questo sia usabile anche per tutte le specializzazioni successivamente costruite (è la situazione tipica)
  • Anche un SortedSet<Student> deve poter usare il Comparator<Person>!
    • Se so confrontare due persone, so confrontare anche due studenti
  • Questo è possibile solo grazie al suo tipo atteso Comparator<? super E>
    • altrimenti, sarebbe invariante e non potrei passare un Comparator<Person> a un SortedSet<Student>

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 getFirst();
    E getLast();
    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

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) {...}

}

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) {...}
}

java.util.Map

mappe associative (o dizionari)

Map<K,V>

  • 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)
  • 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)
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();         // vista sul set dei valori
    Collection<V> values();  // vista sulla collezione di chiavi
}

Implementazioni notevoli di Map

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)
  • Usata preferibilmente

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

package it.unibo.collections.generic.map;

import java.util.LinkedHashMap;
import java.util.Map;

public class UseMap {
    static void main() {
        // Dichiaro l'interfaccia, istanzio un'implementazione concreta
        final Map<Integer, String> map = new LinkedHashMap<>();
        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);

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

Collezioni

Progettazione e Sviluppo del Software

C.D.L. Tecnologie dei Sistemi Informatici

Danilo Pianini — danilo.pianini@unibo.it

Gianluca Aguzzi — gianluca.aguzzi@unibo.it

Angelo Filaseta — angelo.filaseta@unibo.it

Compiled on: 2025-12-05 — versione stampabile

back