1. Il problema dell'ordine nelle collezioni classiche
In Java ci sono sempre state collezioni che garantiscono l'ordine degli elementi (ad esempio, ArrayList, LinkedList, LinkedHashSet, LinkedHashMap) e altre in cui l'ordine non è garantito (ad esempio, HashSet, HashMap). Tuttavia, tutte queste collezioni avevano una mancanza comune: benché alcune di esse mantengano gli elementi in un certo ordine, le interfacce standard (List, Set, Map) non fornivano metodi universali per accedere al primo e all'ultimo elemento o per invertire l'ordine.
Per esempio, se avete un List<String>, potete ottenere il primo elemento con list.get(0), ma per Set o Map questo trucco non funziona — bisogna usare gli iteratori o scrivere codice superfluo. Ammettiamolo, non è molto comodo e non aiuta la leggibilità.
È come avere un armadio con cassetti, ma per prendere il primo o l'ultimo cassetto dovreste ogni volta contarli tutti a mano! Sarebbe bello avere delle maniglie «primo» e «ultimo», giusto?
La nascita di SequencedCollection e delle interfacce correlate
In Java 21 sono state introdotte nuove interfacce di collezioni:
- SequencedCollection<E>
- SequencedSet<E>
- SequencedMap<K, V>
Queste interfacce estendono le collezioni standard e introducono un approccio unificato alla gestione dell'ordine degli elementi. Ora si può scrivere codice universale per qualsiasi collezione in cui l'ordine sia importante, senza preoccuparsi della specifica implementazione.
Che cosa sono?
- SequencedCollection è una collezione in cui gli elementi hanno un ordine definito, e potete facilmente ottenere il primo e l'ultimo elemento, oltre a invertire l'ordine.
- SequencedSet — lo stesso, ma per gli insiemi (elementi unici).
- SequencedMap — lo stesso, ma per le mappe (chiave-valore).
Quali collezioni ora li implementano?
In Java 21 le seguenti collezioni standard implementano le nuove interfacce:
- ArrayList, LinkedList → SequencedCollection
- LinkedHashSet, TreeSet → SequencedSet
- LinkedHashMap, TreeMap → SequencedMap
Ciò significa che, se già usate queste collezioni, ottenete automaticamente le nuove funzionalità!
2. Metodi principali di SequencedCollection, SequencedSet, SequencedMap
Metodi SequencedCollection
E getFirst(); // Ottenere il primo elemento
E getLast(); // Ottenere l'ultimo elemento
SequencedCollection<E> reversed(); // Ottenere la collezione in ordine inverso
Metodi SequencedSet
Gli stessi metodi di SequencedCollection, più tutto ciò che fa Set.
Metodi SequencedMap
Map.Entry<K, V> firstEntry(); // Primo elemento (chiave-valore)
Map.Entry<K, V> lastEntry(); // Ultimo elemento (chiave-valore)
SequencedMap<K, V> reversed(); // Map in ordine inverso
3. Esempi di utilizzo delle nuove interfacce
Esempio 1: ottenere il primo e l'ultimo elemento
import java.util.*;
public class SequencedDemo {
public static void main(String[] args) {
SequencedCollection<String> sc = new ArrayList<>();
sc.add("Java");
sc.add("Python");
sc.add("Kotlin");
// Ottenere il primo e l'ultimo elemento
String first = sc.getFirst(); // "Java"
String last = sc.getLast(); // "Kotlin"
System.out.println("Primo: " + first);
System.out.println("Ultimo: " + last);
}
}
Output:
Primo: Java
Ultimo: Kotlin
Esempio 2: inversione della collezione
SequencedCollection<String> sc = new LinkedList<>();
sc.add("A");
sc.add("B");
sc.add("C");
SequencedCollection<String> reversed = sc.reversed();
System.out.println(reversed); // [C, B, A]
Nota: reversed() restituisce una vista della collezione in ordine inverso. Se modificate la collezione originale, cambierà anche la vista reversed!
Esempio 3: lavorare con SequencedSet
SequencedSet<Integer> set = new LinkedHashSet<>();
set.add(100);
set.add(200);
set.add(300);
System.out.println("Primo elemento: " + set.getFirst()); // 100
System.out.println("Ultimo elemento: " + set.getLast()); // 300
SequencedSet<Integer> reversedSet = set.reversed();
System.out.println(reversedSet); // [300, 200, 100]
Esempio 4: lavorare con SequencedMap
SequencedMap<String, Integer> map = new LinkedHashMap<>();
map.put("apple", 5);
map.put("banana", 3);
map.put("cherry", 7);
Map.Entry<String, Integer> first = map.firstEntry();
Map.Entry<String, Integer> last = map.lastEntry();
System.out.println("Primo: " + first.getKey() + " = " + first.getValue()); // apple = 5
System.out.println("Ultimo: " + last.getKey() + " = " + last.getValue()); // cherry = 7
SequencedMap<String, Integer> reversedMap = map.reversed();
System.out.println(reversedMap); // {cherry=7, banana=3, apple=5}
4. In che modo questo riguarda la vostra applicazione?
Supponiamo che nella vostra applicazione didattica conserviate l'elenco degli utenti che hanno effettuato l'accesso e vogliate ottenere rapidamente il primo e l'ultimo utente (per esempio, per mostrare «chi è entrato per primo» e «chi è l'ultimo»). Prima bisognava scrivere qualcosa del genere:
List<String> users = new ArrayList<>();
// ... aggiungiamo gli utenti
String first = users.get(0);
String last = users.get(users.size() - 1);
Ma se la collezione non è una lista, bensì per esempio un LinkedHashSet (dove gli elementi sono unici e l'ordine è conservato), questo trucco non funziona più:
Set<String> users = new LinkedHashSet<>();
// ... aggiungiamo gli utenti
// Come ottenere il primo? Solo tramite un iteratore:
String first = users.iterator().next();
// E l'ultimo? Bisogna scorrere tutti gli elementi!
Adesso è tutto più semplice e universale:
SequencedSet<String> users = new LinkedHashSet<>();
// ... aggiungiamo gli utenti
String first = users.getFirst();
String last = users.getLast();
Questo non solo riduce la quantità di codice, ma lo rende anche più leggibile e sicuro.
5. Schema delle nuove interfacce
classDiagram
Collection <|-- SequencedCollection
List <|-- SequencedCollection
Set <|-- SequencedSet
Map <|-- SequencedMap
SequencedCollection <|-- SequencedSet
SequencedSet <|-- LinkedHashSet
SequencedSet <|-- TreeSet
SequencedCollection <|-- ArrayList
SequencedCollection <|-- LinkedList
SequencedMap <|-- LinkedHashMap
SequencedMap <|-- TreeMap
6. Dettagli utili
Vantaggi pratici di SequencedCollection
- Un'interfaccia unificata per lavorare con l'ordine: Non serve più ricordare dove c'è get(0), dove è necessario un iteratore e dove non si può proprio ottenere il primo elemento.
- Comodità nel lavorare con code e stack: È facile ottenere il primo (head) e l'ultimo (tail) elemento.
- Sicurezza e leggibilità: Meno errori legati a un uso scorretto delle collezioni; il codice diventa autoesplicativo.
- Inversione rapida della collezione: Il metodo reversed() consente di ottenere facilmente l'ordine inverso senza manipolazioni manuali.
- Semplicità di manutenzione ed estensione del codice: Se in futuro vorrete sostituire, per esempio, ArrayList con LinkedHashSet, il codice che usa SequencedCollection non dovrà essere riscritto.
Caratteristiche di implementazione
- Non tutte le collezioni implementano SequencedCollection: Per esempio, HashSet e HashMap non garantiscono l'ordine, quindi non implementano le nuove interfacce.
- I metodi possono lanciare eccezioni: Se la collezione è vuota, una chiamata a getFirst() o getLast() provocherà una NoSuchElementException. Non dimenticate di verificare che la collezione non sia vuota!
- reversed() è una vista, non una copia: Le modifiche alla collezione originale si riflettono nella vista invertita e viceversa.
- Compatibilità: Le nuove interfacce sono disponibili solo a partire da Java 21. Se usate una versione precedente di Java, queste funzionalità non sono ancora disponibili (ma è un'ottima occasione per aggiornare!).
- Generics: Tutte le nuove interfacce supportano completamente i generics, quindi si può lavorare con qualsiasi tipo di dato.
7. Errori tipici nell'uso di SequencedCollection
Errore n. 1: aspettarsi il supporto dell'ordine da collezioni che non lo garantiscono. Se provate a fare il cast di HashSet a SequencedSet, otterrete un errore di compilazione — HashSet non ha un ordine e non implementa questa interfaccia.
Errore n. 2: ignorare le collezioni vuote. Chiamare getFirst() o getLast() su una collezione vuota lancerà un'eccezione. Prima di usare questi metodi, verificate che la collezione non sia vuota:
if (!sc.isEmpty()) {
String first = sc.getFirst();
}
Errore n. 3: fraintendimenti con reversed(). Il metodo reversed() restituisce una vista, non una copia. Se modificate la vista reversed, cambierà anche la collezione originale (e viceversa). Ciò può portare a risultati inattesi se non vi aspettate questo comportamento.
Errore n. 4: usare le nuove interfacce su versioni vecchie di Java. Se il vostro progetto è compilato con una versione inferiore a Java 21, il compilatore non troverà queste interfacce. Controllate la versione del JDK nelle impostazioni del progetto!
GO TO FULL VERSION