1. Introduzione
Nella libreria standard di Java esistono molte collezioni, ma quando si parla di intervalli, ricerca degli elementi «più vicini», prioritizzazione e gestione di slot temporali, entrano in gioco le interfacce NavigableSet e NavigableMap.
NavigableSet estende SortedSet, aggiungendo metodi per cercare elementi rispetto a un valore dato (minore, maggiore, più vicino, ecc.) e per lavorare con gli intervalli.
NavigableMap estende SortedMap, aggiungendo metodi simili per la ricerca per chiavi e per lavorare con gli intervalli.
Le implementazioni più note: TreeSet e TreeMap.
Quando servono NavigableSet/NavigableMap?
- Quando è importante non solo memorizzare elementi/chiavi univoci in ordine, ma anche trovare rapidamente i «vicini» (ad esempio, il prossimo orario libero, il task con priorità, un intervallo di valori).
- Quando devi lavorare con intervalli: «tutti gli elementi tra X e Y», «tutte le chiavi maggiori/minori di una data», «trovare la chiave più vicina».
2. Intervalli: subSet, headSet, tailSet
Metodi per lavorare con gli intervalli
NavigableSet e NavigableMap permettono di ottenere rappresentazioni «live» (view) su sottoinsiemi della collezione:
- subSet(fromElement, fromInclusive, toElement, toInclusive) — elementi nell’intervallo [fromElement; toElement], inclusivo/esclusivo.
- headSet(toElement, inclusive) — tutti gli elementi minori (o minori o uguali) di toElement.
- tailSet(fromElement, inclusive) — tutti gli elementi maggiori (o maggiori o uguali) di fromElement.
Esempio:
NavigableSet<Integer> set = new TreeSet<>(List.of(10, 20, 30, 40, 50));
// Intervallo da 15 (inclusivo) a 45 (esclusivo)
NavigableSet<Integer> sub = set.subSet(15, true, 45, false); // [20, 30, 40]
System.out.println(sub); // [20, 30, 40]
// Tutti gli elementi <= 30
NavigableSet<Integer> head = set.headSet(30, true); // [10, 20, 30]
// Tutti gli elementi > 20
NavigableSet<Integer> tail = set.tailSet(20, false); // [30, 40, 50]
Inclusività/esclusività:
- true — inclusivo (l’elemento rientra nell’intervallo)
- false — esclusivo (l’elemento non rientra)
Per NavigableMap i metodi sono analoghi, ma operano sulle chiavi.
3. Operazioni sui valori vicini: floor, ceiling, lower, higher; pollFirst/Last
Ricerca degli elementi più vicini
NavigableSet:
- lower(E e) — il massimo elemento < e (strettamente minore)
- floor(E e) — il massimo elemento ≤ e (minore o uguale)
- ceiling(E e) — il minimo elemento ≥ e (maggiore o uguale)
- higher(E e) — il minimo elemento > e (strettamente maggiore)
Esempio:
NavigableSet<Integer> set = new TreeSet<>(List.of(10, 20, 30, 40, 50));
System.out.println(set.lower(30)); // 20
System.out.println(set.floor(30)); // 30
System.out.println(set.ceiling(25)); // 30
System.out.println(set.higher(40)); // 50
pollFirst() / pollLast()
- pollFirst() — rimuove e restituisce l’elemento minimo (oppure null se vuoto)
- pollLast() — rimuove e restituisce l’elemento massimo
Esempio:
System.out.println(set.pollFirst()); // 10
System.out.println(set.pollLast()); // 50
System.out.println(set); // [20, 30, 40]
NavigableMap: metodi analoghi per le chiavi — lowerKey, floorKey, ceilingKey, higherKey, nonché pollFirstEntry, pollLastEntry.
4. Viste: descendingSet/descendingMap; view e la loro «vivacità»
Ordine inverso: descendingSet/descendingMap
- descendingSet() — restituisce una vista dell’insieme in ordine inverso.
- descendingMap() — restituisce una vista della mappa in ordine inverso delle chiavi.
Esempio:
NavigableSet<Integer> set = new TreeSet<>(List.of(10, 20, 30, 40, 50));
NavigableSet<Integer> desc = set.descendingSet();
System.out.println(desc); // [50, 40, 30, 20, 10]
Importante: non è una copia, ma una vista «live» (view) sullo stesso insieme di dati. Le modifiche in una si riflettono nell’altra.
Viste (view) e la loro «vivacità»
- I metodi subSet, headSet, tailSet, descendingSet, descendingMap restituiscono una view — una rappresentazione «live» della collezione originale.
- Qualsiasi modifica nella view si riflette nella collezione di origine e viceversa.
- Se rimuovi un elemento da subSet, scomparirà anche dall’insieme originale.
Esempio:
NavigableSet<Integer> set = new TreeSet<>(List.of(10, 20, 30, 40, 50));
NavigableSet<Integer> sub = set.subSet(20, true, 40, true); // [20, 30, 40]
sub.remove(30);
System.out.println(set); // [10, 20, 40, 50]
Attenzione: se modifichi la collezione originale in modo che un elemento «esca» dall’intervallo della view, al successivo accesso alla view otterrai ConcurrentModificationException.
5. Casi d’uso di NavigableSet/NavigableMap
1. Slot temporali/pianificazioni
Obiettivo: trovare lo slot temporale libero più vicino per un incontro.
NavigableSet<LocalTime> slots = new TreeSet<>(List.of(
LocalTime.of(9, 0),
LocalTime.of(10, 0),
LocalTime.of(11, 0),
LocalTime.of(14, 0)
));
LocalTime requested = LocalTime.of(10, 30);
LocalTime nextSlot = slots.ceiling(requested); // 11:00
System.out.println("Orario più vicino: " + nextSlot);
2. Prioritizzazione (coda con priorità)
Obiettivo: ottenere sempre rapidamente l’elemento con priorità massima/minima.
NavigableSet<Task> tasks = new TreeSet<>(Comparator.comparingInt(Task::priority));
tasks.add(new Task("Posta", 2));
tasks.add(new Task("Chiamata", 1));
tasks.add(new Task("Report", 3));
Task next = tasks.pollFirst(); // Attività con priorità più alta (1)
3. «Chiave più vicina» (ad esempio per cercare un intervallo in Map)
Obiettivo: trovare l’intervallo di valori per una chiave o la chiave più vicina.
NavigableMap<Integer, String> grades = new TreeMap<>();
grades.put(50, "Insufficiente");
grades.put(65, "Sufficiente");
grades.put(75, "Buono");
grades.put(85, "Ottimo");
int score = 78;
int key = grades.floorKey(score); // 75
System.out.println("Voto: " + grades.get(key)); // Buono
6. Pratica: mini‑esempi
Esempio 1: Intervallo di date
NavigableSet<LocalDate> holidays = new TreeSet<>(List.of(
LocalDate.of(2024, 1, 1),
LocalDate.of(2024, 5, 1),
LocalDate.of(2024, 12, 31)
));
LocalDate from = LocalDate.of(2024, 1, 1);
LocalDate to = LocalDate.of(2024, 6, 1);
NavigableSet<LocalDate> spring = holidays.subSet(from, true, to, false);
System.out.println(spring); // [2024-01-01, 2024-05-01]
Esempio 2: Ricerca del valore più vicino
NavigableSet<Integer> numbers = new TreeSet<>(List.of(10, 20, 30, 40, 50));
int x = 25;
System.out.println("Minore: " + numbers.lower(x)); // 20
System.out.println("Minore o uguale: " + numbers.floor(x)); // 20
System.out.println("Maggiore o uguale: " + numbers.ceiling(x)); // 30
System.out.println("Maggiore: " + numbers.higher(x)); // 30
7. Errori tipici e sfumature
Errore n. 1: confusione tra inclusività/esclusività in subSet/headSet/tailSet.
Controlla sempre con attenzione i parametri inclusive — nelle vecchie versioni di Java gli intervalli erano esclusivi per impostazione predefinita, in NavigableSet/NavigableMap è possibile indicare esplicitamente inclusivo/esclusivo.
Errore n. 2: aspettarsi che una view sia una copia.
Una view è una rappresentazione «live», non una copia! Le modifiche nella view si riflettono nella collezione originale.
Errore n. 3: ConcurrentModificationException quando si modifica la collezione originale al di fuori della view.
Se modifichi la collezione originale in modo che un elemento «esca» dall’intervallo della view, al successivo accesso alla view otterrai un’eccezione.
Errore n. 4: uso di NavigableSet/NavigableMap senza Comparable o Comparator.
Queste collezioni richiedono che gli elementi (o le chiavi) siano confrontabili (Comparable) o che venga passato un Comparator. In caso contrario otterrai ClassCastException.
Errore n. 5: aspettarsi che pollFirst/pollLast non modifichino la collezione.
Questi metodi rimuovono elementi! Se ti serve solo la lettura — usa first()/last().
GO TO FULL VERSION