Salta al contenuto principale

07 - Collections Framework: List, Set e Map

Framework Collections di Java: ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap, Queue e algoritmi della classe Collections.

Cosa ne pensi di questo articolo?

07 - Collections Framework: List, Set e Map
JavaFoto:Daniil KomovPexels

In Breve

Framework Collections di Java: ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap, Queue e algoritmi della classe Collections.

Il Collections Framework

Il Collections Framework di Java è un'architettura unificata per rappresentare e manipolare gruppi di oggetti. Fornisce interfacce, implementazioni e algoritmi per gestire dati in modo efficiente.

Cosa Imparerai

  • Gerarchia delle interfacce Collection
  • List: ArrayList e LinkedList
  • Set: HashSet, TreeSet e LinkedHashSet
  • Map: HashMap, TreeMap e LinkedHashMap
  • Queue e Deque
  • Algoritmi e utility di Collections

Gerarchia delle Collections

Interfacce Principali

Interfaccia Descrizione Implementazioni
Collection Interfaccia radice -
List Sequenza ordinata con duplicati ArrayList, LinkedList
Set Elementi unici HashSet, TreeSet
Queue Coda FIFO LinkedList, PriorityQueue
Map Coppie chiave-valore HashMap, TreeMap
Struttura del Framework
                    Iterable
                        |
                   Collection
                   /    |    \
                List   Set   Queue
                 |      |      |
           ArrayList HashSet LinkedList
           LinkedList TreeSet PriorityQueue

                      Map (separata)
                       |
                  HashMap TreeMap

List - Liste Ordinate

List mantiene gli elementi in ordine di inserimento e permette duplicati. È indicizzata (accesso per posizione).

ArrayList

ArrayList - Array Dinamico
import java.util.ArrayList;
import java.util.List;

public class RegistroStudenti {
    public static void main(String[] args) {
        // Creazione lista
        List<String> studenti = new ArrayList<>();

        // Aggiunta elementi
        studenti.add("Mario Rossi");
        studenti.add("Laura Bianchi");
        studenti.add("Giuseppe Verdi");
        studenti.add(1, "Anna Neri");  // Inserisce in posizione 1

        // Accesso per indice
        String primo = studenti.get(0);        // "Mario Rossi"
        String secondo = studenti.get(1);      // "Anna Neri"

        // Dimensione
        int numStudenti = studenti.size();     // 4

        // Modifica elemento
        studenti.set(0, "Marco Rossi");

        // Ricerca
        boolean presente = studenti.contains("Laura Bianchi");  // true
        int posizione = studenti.indexOf("Giuseppe Verdi");     // 3

        // Rimozione
        studenti.remove("Anna Neri");          // per valore
        studenti.remove(0);                    // per indice

        // Iterazione
        for (String studente : studenti) {
            System.out.println(studente);
        }

        // Iterazione con indice
        for (int i = 0; i < studenti.size(); i++) {
            System.out.println((i + 1) + ". " + studenti.get(i));
        }
    }
}

LinkedList

LinkedList - Lista Concatenata
import java.util.LinkedList;
import java.util.List;

public class CodaEsami {
    public static void main(String[] args) {
        LinkedList<String> coda = new LinkedList<>();

        // Operazioni specifiche LinkedList
        coda.addFirst("Studente urgente");     // In testa
        coda.addLast("Studente normale");      // In coda
        coda.add("Altro studente");            // In coda

        // Accesso primi/ultimi
        String primo = coda.getFirst();
        String ultimo = coda.getLast();
        String primoPeek = coda.peekFirst();   // Non rimuove

        // Rimozione primi/ultimi
        String rimossoPrimo = coda.removeFirst();
        String rimossoUltimo = coda.removeLast();

        // Uso come Stack (LIFO)
        coda.push("Nuovo in cima");
        String estratto = coda.pop();

        // Uso come Queue (FIFO)
        coda.offer("Nuovo in coda");
        String servito = coda.poll();
    }
}

ArrayList vs LinkedList

Operazione ArrayList LinkedList
get(index) O(1) - Veloce O(n) - Lento
add(element) O(1) ammortizzato O(1)
add(0, element) O(n) - Lento O(1) - Veloce
remove(0) O(n) - Lento O(1) - Veloce
Memoria Meno overhead Più overhead (nodi)

Consiglio: Usa ArrayList come default, LinkedList solo per frequenti inserimenti/rimozioni in testa.

Set - Insiemi Unici

Set non ammette duplicati. È ideale quando serve garantire l'unicità degli elementi.

HashSet

HashSet - Veloce ma Non Ordinato
import java.util.HashSet;
import java.util.Set;

public class CorsiUnici {
    public static void main(String[] args) {
        Set<String> corsiFrequentati = new HashSet<>();

        // Aggiunta elementi
        corsiFrequentati.add("Programmazione");
        corsiFrequentati.add("Database");
        corsiFrequentati.add("Reti");
        corsiFrequentati.add("Programmazione");  // Duplicato ignorato!

        System.out.println(corsiFrequentati.size());  // 3

        // Verifica presenza
        if (corsiFrequentati.contains("Database")) {
            System.out.println("Corso già frequentato");
        }

        // Rimozione
        corsiFrequentati.remove("Reti");

        // Iterazione (ordine NON garantito)
        for (String corso : corsiFrequentati) {
            System.out.println(corso);
        }

        // Operazioni insiemistiche
        Set<String> nuoviCorsi = new HashSet<>();
        nuoviCorsi.add("AI");
        nuoviCorsi.add("Database");

        // Unione
        Set<String> tutti = new HashSet<>(corsiFrequentati);
        tutti.addAll(nuoviCorsi);

        // Intersezione
        Set<String> comuni = new HashSet<>(corsiFrequentati);
        comuni.retainAll(nuoviCorsi);

        // Differenza
        Set<String> soloVecchi = new HashSet<>(corsiFrequentati);
        soloVecchi.removeAll(nuoviCorsi);
    }
}

TreeSet e LinkedHashSet

TreeSet - Ordinato Naturalmente
import java.util.TreeSet;
import java.util.LinkedHashSet;
import java.util.Set;

public class TipiSet {
    public static void main(String[] args) {
        // TreeSet: ordinato automaticamente
        Set<Integer> voti = new TreeSet<>();
        voti.add(28);
        voti.add(30);
        voti.add(24);
        voti.add(27);

        System.out.println(voti);  // [24, 27, 28, 30] - ordinato!

        // Metodi specifici TreeSet
        TreeSet<Integer> votiTree = new TreeSet<>(voti);
        Integer minimo = votiTree.first();       // 24
        Integer massimo = votiTree.last();       // 30
        Integer inferiore = votiTree.lower(28);  // 27 (strettamente <)
        Integer superiore = votiTree.higher(28); // 30 (strettamente >)

        // Subset
        Set<Integer> medi = votiTree.subSet(25, 29);  // [27, 28]

        // LinkedHashSet: mantiene ordine di inserimento
        Set<String> ordineIscrizione = new LinkedHashSet<>();
        ordineIscrizione.add("Mario");
        ordineIscrizione.add("Anna");
        ordineIscrizione.add("Luigi");

        // Iterazione nell'ordine di inserimento
        for (String nome : ordineIscrizione) {
            System.out.println(nome);  // Mario, Anna, Luigi
        }
    }
}

Confronto Set

Tipo Ordine Performance Uso tipico
HashSet Nessuno O(1) Massima velocità
TreeSet Naturale/Comparator O(log n) Elementi ordinati
LinkedHashSet Inserimento O(1) Ordine preservato

Map - Coppie Chiave-Valore

Map associa chiavi uniche a valori. È la struttura dati più usata per lookup veloci e dizionari.

HashMap

HashMap - Dizionario Veloce
import java.util.HashMap;
import java.util.Map;

public class RegistroVoti {
    public static void main(String[] args) {
        // Mappa studente -> voto
        Map<String, Integer> voti = new HashMap<>();

        // Inserimento
        voti.put("Mario Rossi", 28);
        voti.put("Laura Bianchi", 30);
        voti.put("Giuseppe Verdi", 25);

        // Accesso
        Integer votoMario = voti.get("Mario Rossi");  // 28
        Integer votoAssente = voti.get("Inesistente"); // null

        // getOrDefault: evita null
        Integer votoSicuro = voti.getOrDefault("Inesistente", 0);

        // Verifica presenza
        if (voti.containsKey("Laura Bianchi")) {
            System.out.println("Laura è registrata");
        }

        if (voti.containsValue(30)) {
            System.out.println("Qualcuno ha preso 30!");
        }

        // Aggiornamento
        voti.put("Mario Rossi", 29);  // Sovrascrive

        // putIfAbsent: inserisce solo se assente
        voti.putIfAbsent("Mario Rossi", 18);  // Non cambia (già presente)

        // Rimozione
        voti.remove("Giuseppe Verdi");
        voti.remove("Laura Bianchi", 30);  // Rimuove solo se valore corrisponde

        // Dimensione
        int numStudenti = voti.size();
        boolean vuota = voti.isEmpty();
    }
}
Iterazione su Map
Map<String, Integer> voti = new HashMap<>();
voti.put("Mario", 28);
voti.put("Laura", 30);
voti.put("Giuseppe", 25);

// Iterazione su chiavi
for (String studente : voti.keySet()) {
    System.out.println(studente);
}

// Iterazione su valori
for (Integer voto : voti.values()) {
    System.out.println(voto);
}

// Iterazione su coppie (Entry) - più efficiente
for (Map.Entry<String, Integer> entry : voti.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// forEach con lambda (Java 8+)
voti.forEach((studente, voto) ->
    System.out.println(studente + " ha preso " + voto)
);

TreeMap e LinkedHashMap

TreeMap - Ordinato per Chiave
import java.util.TreeMap;
import java.util.LinkedHashMap;

public class TipiMap {
    public static void main(String[] args) {
        // TreeMap: chiavi ordinate
        TreeMap<String, Integer> alfabetico = new TreeMap<>();
        alfabetico.put("Zeta", 1);
        alfabetico.put("Alfa", 2);
        alfabetico.put("Beta", 3);

        // Iterazione in ordine alfabetico
        for (String key : alfabetico.keySet()) {
            System.out.println(key);  // Alfa, Beta, Zeta
        }

        // Metodi navigazione
        String prima = alfabetico.firstKey();    // "Alfa"
        String ultima = alfabetico.lastKey();    // "Zeta"
        String inferiore = alfabetico.lowerKey("Beta");  // "Alfa"

        // LinkedHashMap: ordine di inserimento
        LinkedHashMap<String, Integer> ordinato = new LinkedHashMap<>();
        ordinato.put("Primo", 1);
        ordinato.put("Secondo", 2);
        ordinato.put("Terzo", 3);

        // Mantiene ordine di inserimento
        for (String key : ordinato.keySet()) {
            System.out.println(key);  // Primo, Secondo, Terzo
        }
    }
}

Queue e Deque

Queue rappresenta una coda FIFO (First-In-First-Out). Deque è una coda a doppia estremità.

Queue e PriorityQueue
import java.util.Queue;
import java.util.LinkedList;
import java.util.PriorityQueue;

public class GestioneCode {
    public static void main(String[] args) {
        // Coda FIFO con LinkedList
        Queue<String> codaSportello = new LinkedList<>();

        // offer: aggiunge in coda
        codaSportello.offer("Cliente 1");
        codaSportello.offer("Cliente 2");
        codaSportello.offer("Cliente 3");

        // peek: guarda primo senza rimuovere
        String prossimo = codaSportello.peek();  // "Cliente 1"

        // poll: rimuove e restituisce primo
        String servito = codaSportello.poll();   // "Cliente 1"
        System.out.println("Servito: " + servito);

        // PriorityQueue: ordine per priorità
        Queue<Integer> priorità = new PriorityQueue<>();
        priorità.offer(30);
        priorità.offer(10);
        priorità.offer(20);

        // Estrae in ordine di priorità (naturale = crescente)
        while (!priorità.isEmpty()) {
            System.out.println(priorità.poll());  // 10, 20, 30
        }

        // PriorityQueue con ordine personalizzato
        Queue<String> perLunghezza = new PriorityQueue<>(
            (a, b) -> a.length() - b.length()
        );
        perLunghezza.offer("lungo");
        perLunghezza.offer("xx");
        perLunghezza.offer("medio");

        while (!perLunghezza.isEmpty()) {
            System.out.println(perLunghezza.poll());  // xx, lungo, medio
        }
    }
}
Deque - Coda a Doppia Estremità
import java.util.Deque;
import java.util.ArrayDeque;

public class CodaDoppia {
    public static void main(String[] args) {
        Deque<String> deque = new ArrayDeque<>();

        // Operazioni in testa
        deque.addFirst("A");
        deque.offerFirst("B");

        // Operazioni in coda
        deque.addLast("C");
        deque.offerLast("D");

        // Stato: [B, A, C, D]

        // Rimozione da testa
        String primo = deque.removeFirst();  // "B"
        String primoPoll = deque.pollFirst(); // "A"

        // Rimozione da coda
        String ultimo = deque.removeLast();  // "D"
        String ultimoPoll = deque.pollLast(); // "C"

        // Uso come Stack (LIFO)
        Deque<String> stack = new ArrayDeque<>();
        stack.push("Primo");   // In cima
        stack.push("Secondo");
        stack.push("Terzo");

        while (!stack.isEmpty()) {
            System.out.println(stack.pop());  // Terzo, Secondo, Primo
        }
    }
}

Algoritmi della Classe Collections

La classe Collections fornisce metodi statici per operazioni comuni sulle collezioni.

Utility Collections
import java.util.*;

public class UtilityCollections {
    public static void main(String[] args) {
        List<Integer> voti = new ArrayList<>(
            Arrays.asList(25, 30, 28, 24, 27, 30)
        );

        // Ordinamento
        Collections.sort(voti);
        System.out.println(voti);  // [24, 25, 27, 28, 30, 30]

        // Ordinamento inverso
        Collections.sort(voti, Collections.reverseOrder());
        System.out.println(voti);  // [30, 30, 28, 27, 25, 24]

        // Ricerca binaria (lista deve essere ordinata!)
        Collections.sort(voti);
        int indice = Collections.binarySearch(voti, 27);

        // Min e Max
        int minimo = Collections.min(voti);  // 24
        int massimo = Collections.max(voti); // 30

        // Frequenza
        int cont30 = Collections.frequency(voti, 30);  // 2

        // Mescolare (shuffle)
        Collections.shuffle(voti);

        // Invertire
        Collections.reverse(voti);

        // Riempire
        Collections.fill(voti, 0);  // Tutti a 0

        // Scambiare elementi
        Collections.swap(voti, 0, 1);

        // Rotazione
        List<String> lista = new ArrayList<>(
            Arrays.asList("A", "B", "C", "D")
        );
        Collections.rotate(lista, 1);   // [D, A, B, C]
        Collections.rotate(lista, -1);  // [A, B, C, D]

        // Liste immutabili
        List<String> immutabile = Collections.unmodifiableList(lista);
        // immutabile.add("X"); // UnsupportedOperationException!

        // Lista vuota immutabile
        List<String> vuota = Collections.emptyList();

        // Singleton (lista con un elemento)
        List<String> singolo = Collections.singletonList("Unico");
    }
}

Esempio Completo: Sistema Gestione Corsi

Corso.java
package scuola;

import java.util.*;

public class Corso {
    private String nome;
    private String docente;
    private int maxStudenti;
    private Set<String> studentiIscritti;
    private Map<String, List<Integer>> votiStudenti;

    public Corso(String nome, String docente, int maxStudenti) {
        this.nome = nome;
        this.docente = docente;
        this.maxStudenti = maxStudenti;
        this.studentiIscritti = new LinkedHashSet<>();
        this.votiStudenti = new HashMap<>();
    }

    public boolean iscrivi(String studente) {
        if (studentiIscritti.size() >= maxStudenti) {
            System.out.println("Corso pieno!");
            return false;
        }
        if (studentiIscritti.contains(studente)) {
            System.out.println(studente + " già iscritto!");
            return false;
        }
        studentiIscritti.add(studente);
        votiStudenti.put(studente, new ArrayList<>());
        return true;
    }

    public void registraVoto(String studente, int voto) {
        if (!studentiIscritti.contains(studente)) {
            System.out.println(studente + " non iscritto!");
            return;
        }
        if (voto < 18 || voto > 30) {
            System.out.println("Voto non valido");
            return;
        }
        votiStudenti.get(studente).add(voto);
    }

    public double calcolaMedia(String studente) {
        List<Integer> voti = votiStudenti.get(studente);
        if (voti == null || voti.isEmpty()) {
            return 0;
        }
        return voti.stream()
                   .mapToInt(Integer::intValue)
                   .average()
                   .orElse(0);
    }

    public Map<String, Double> classificaStudenti() {
        Map<String, Double> classifica = new TreeMap<>();
        for (String studente : studentiIscritti) {
            classifica.put(studente, calcolaMedia(studente));
        }
        return classifica;
    }

    public List<String> getStudentiOrdinatiPerMedia() {
        List<String> ordinati = new ArrayList<>(studentiIscritti);
        ordinati.sort((s1, s2) ->
            Double.compare(calcolaMedia(s2), calcolaMedia(s1))
        );
        return ordinati;
    }

    public void stampaRiepilogo() {
        System.out.println("╔══════════════════════════════════════╗");
        System.out.println("║ Corso: " + nome);
        System.out.println("║ Docente: " + docente);
        System.out.println("║ Iscritti: " + studentiIscritti.size() + "/" + maxStudenti);
        System.out.println("╠══════════════════════════════════════╣");

        for (String studente : getStudentiOrdinatiPerMedia()) {
            List<Integer> voti = votiStudenti.get(studente);
            double media = calcolaMedia(studente);
            System.out.printf("║ %-20s Media: %.2f%n", studente, media);
            System.out.println("║   Voti: " + voti);
        }

        System.out.println("╚══════════════════════════════════════╝");
    }

    // Getters
    public String getNome() { return nome; }
    public Set<String> getStudentiIscritti() {
        return Collections.unmodifiableSet(studentiIscritti);
    }
}
GestioneCorsi.java
package scuola;

import java.util.*;

public class GestioneCorsi {
    private Map<String, Corso> corsi;
    private Map<String, Set<String>> iscrizioniStudente;

    public GestioneCorsi() {
        this.corsi = new HashMap<>();
        this.iscrizioniStudente = new HashMap<>();
    }

    public void aggiungiCorso(Corso corso) {
        corsi.put(corso.getNome(), corso);
    }

    public boolean iscriviStudente(String studente, String nomeCorso) {
        Corso corso = corsi.get(nomeCorso);
        if (corso == null) {
            System.out.println("Corso non trovato: " + nomeCorso);
            return false;
        }

        if (corso.iscrivi(studente)) {
            iscrizioniStudente
                .computeIfAbsent(studente, k -> new HashSet<>())
                .add(nomeCorso);
            return true;
        }
        return false;
    }

    public Set<String> getCorsiStudente(String studente) {
        return iscrizioniStudente.getOrDefault(studente, Collections.emptySet());
    }

    public void stampaStatistiche() {
        System.out.println("\n=== STATISTICHE GLOBALI ===");
        System.out.println("Totale corsi: " + corsi.size());

        int totaleIscrizioni = iscrizioniStudente.values()
            .stream()
            .mapToInt(Set::size)
            .sum();
        System.out.println("Totale iscrizioni: " + totaleIscrizioni);

        // Studente con più corsi
        String piuAttivo = iscrizioniStudente.entrySet()
            .stream()
            .max(Comparator.comparingInt(e -> e.getValue().size()))
            .map(Map.Entry::getKey)
            .orElse("Nessuno");
        System.out.println("Studente più attivo: " + piuAttivo);
    }

    public static void main(String[] args) {
        GestioneCorsi gestione = new GestioneCorsi();

        // Crea corsi
        gestione.aggiungiCorso(new Corso("Programmazione", "Prof. Bianchi", 30));
        gestione.aggiungiCorso(new Corso("Database", "Prof. Rossi", 25));
        gestione.aggiungiCorso(new Corso("Reti", "Prof. Verdi", 20));

        // Iscrivi studenti
        gestione.iscriviStudente("Mario", "Programmazione");
        gestione.iscriviStudente("Mario", "Database");
        gestione.iscriviStudente("Laura", "Programmazione");
        gestione.iscriviStudente("Laura", "Reti");
        gestione.iscriviStudente("Giuseppe", "Database");

        // Registra voti
        Corso prog = gestione.corsi.get("Programmazione");
        prog.registraVoto("Mario", 28);
        prog.registraVoto("Mario", 30);
        prog.registraVoto("Laura", 27);

        Corso db = gestione.corsi.get("Database");
        db.registraVoto("Mario", 25);
        db.registraVoto("Giuseppe", 30);

        // Stampa riepilogo
        prog.stampaRiepilogo();
        db.stampaRiepilogo();

        // Statistiche
        gestione.stampaStatistiche();

        System.out.println("\nCorsi di Mario: " + gestione.getCorsiStudente("Mario"));
    }
}

Conclusione

Il Collections Framework è fondamentale per ogni sviluppatore Java. La scelta della collezione giusta dipende dai requisiti:

Punti Chiave da Ricordare

  • ArrayList: Accesso per indice veloce, default per liste
  • LinkedList: Inserimenti/rimozioni in testa veloci
  • HashSet: Unicità con massima velocità
  • TreeSet: Unicità con ordinamento
  • HashMap: Lookup veloce chiave-valore
  • TreeMap: Chiavi ordinate
  • PriorityQueue: Estrazione per priorità
  • ArrayDeque: Stack/Queue efficiente

Nel prossimo articolo affronteremo la gestione degli errori e delle eccezioni: try-catch, throw, eccezioni checked e unchecked.

Domande frequenti

Qual è l'architettura unificata per rappresentare e manipolare gruppi di oggetti in Java?

Il Collections Framework.

Quali sono le caratteristiche delle liste ordinate nel Collections Framework?

Mantiene gli elementi in ordine di inserimento e permette duplicati, è indicizzata (accesso per posizione).

Qual è la differenza principale tra un insieme e una lista nell'ambito del Collections Framework?

Un insieme non ammette duplicati, mentre una lista può avere elementi duplicati.

Qual è il tipo di struttura dati più utilizzato per lookup veloci e dizionari nel Collections Framework?

La coppia chiave-valore (Map).

Fonti e approfondimenti

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti