Il 19 settembre 2023 è stata rilasciata una nuova versione di Java 21. Si tratta di una versione LTS. Si tratta di una versione LTS e queste sono le caratteristiche che la contraddistinguono:
In questo articolo daremo uno sguardo più approfondito alla JEP 431, le Sequenced Collections.
Il filosofo danese Sơren Kierkegaard sostiene che la comprensione degli eventi può avvenire solo dopo averli vissuti: “La vita può essere compresa solo all’indietro; ma deve essere vissuta in avanti”. Gli sviluppatori della versione 21 di Java si sono probabilmente ispirati a queste parole quando hanno introdotto nuove interfacce per rappresentare collezioni con un ordine di occorrenza definito. Ognuna di queste collezioni ha un primo elemento ben definito, un secondo elemento e così via, fino all’ultimo elemento. Inoltre, vengono fornite API uniformi per accedere al primo e all’ultimo elemento ed elaborare i loro elementi in ordine inverso.
Accesso ordinato nelle collezioni Java
Un problema di lunga data del framework delle collezioni di Java è la mancanza di un singolo tipo di collezione che garantisca un ordine specifico per i suoi elementi. Questo ha causato problemi e frustrazioni agli sviluppatori. Inoltre, il framework è storicamente privo di metodi coerenti per accedere ai primi e agli ultimi elementi delle collezioni o per iterare facilmente le collezioni in ordine inverso.
Consideriamo collections come List
e Deque
. Entrambe mantengono un ordine particolare per i loro elementi, ma il loro supertipo comune, Collection
, non garantisce l’ordine. Allo stesso modo, Set non ha un ordine, ma alcuni dei suoi sottotipi, come SortedSet
e LinkedHashS
et, lo hanno. Questa incoerenza significa che il supporto per l’accesso ordinato è disperso nella gerarchia delle collezioni e i metodi per lavorare con l’ordine sono incoerenti o del tutto assenti.
Le nuove implementazioni delle collezioni sequenziate di Java
Ecco una descrizione dettagliata di come gli sviluppatori hanno integrato le collezioni sequenziate nell’esistente Java Collections Framework.
Adattamenti della superinterfaccia
List
eDeque
: Queste due interfacce, che già mantengono l’ordine, ora estendono direttamenteSequencedCollection
. Questo assicura che ereditino le funzionalità relative all’accesso al primo e all’ultimo elemento, oltre a ottenere una vista inversa.LinkedHashSet
: Questa implementazione dell’insieme, che preserva l’ordine di inserimento, ora implementa ancheSequencedSet
. Ciò consente di utilizzare metodi specifici per gli insiemi ordinati, come l’aggiunta di elementi in posizioni specifiche, pur mantenendo l’ordine complessivo.SortedSet
: Questa interfaccia, che mantiene intrinsecamente l’ordine in base a un criterio di ordinamento, ora estendeSequencedSet
. Questo rafforza la natura ordinata di SortedSet e fornisce l’accesso a funzioni comefirst()
elast()
. È importante notare che le implementazioni diSortedSet
comeTreeSet
possono ancora utilizzare la loro logica di ordinamento specifica insieme ai metodi diSequencedSet
.LinkedHashMap
eSortedMap
: Simili aLinkedHashSet
eSortedSet
, queste interfacce, che mantengono l’ordine in base all’inserimento o all’ordinamento delle chiavi rispettivamente, ora implementano ancheSequencedMap
. Ciò consente loro di beneficiare di metodi comefirstEntry()
elastEntry()
, progettati specificamente per le mappe ordinate.
Sovrascritture covarianti per l’inversione
Per garantire la sicurezza dei tipi e la chiarezza, gli sviluppatori hanno implementato delle sovrascritture covarianti per il metodo reversed ()
Ciò significa che quando viene chiamato su uno specifico tipo di collezione sequenziata (ad esempio, List
), anche la vista invertita restituita sarà di quel tipo specifico (ad esempio, List
). Questo evita potenziali problemi con tipi di ritorno inaspettati. Ad esempio, List::reversed
è sovrascritto per restituire un List
invece di una SequencedCollection
.
Nuovi metodi nella classe Collections
La classe di utilità Collections è stata estesa con nuovi metodi per creare wrapper non modificabili per i tre nuovi tipi di sequenze:
Collections.unmodifiableSequencedCollection(sequencedCollection)
: Questo metodo prende unaSequencedCollection
e ne restituisce una versione non modificabile. Ciò consente agli sviluppatori di creare versioni di sola lettura di collezioni sequenziate, per evitare modifiche accidentali.Collections.unmodifiableSequencedSet(sequencedSet)
: Simile al metodo precedente, prende unSequencedSet
e restituisce la sua controparte non modificabile.Collections.unmodifiableSequencedMap(sequencedMap)
: Questo metodo crea una versione non modificabile di unaSequencedMap
.
Questi nuovi metodi promuovono l’immutabilità dei dati, che può essere utile per la sicurezza e la protezione dei thread in ambienti concorrenti. Integrando le Sequenced Collections in questo modo, gli sviluppatori hanno migliorato in modo significativo la coerenza e l’usabilità delle collezioni ordinate nel Java Collections Framework.
Esplorare la funzionalità delle Sequenced Collections
Vediamo come queste interfacce vengono utilizzate in pratica:
Elaborazioni di un file di log di grandi dimensioni in ordine inverso
Immaginiamo uno scenario in cui sia necessario analizzare un file di log di grandi dimensioni, iniziando dalle voci più recenti. L’approccio più semplice sarebbe quello di leggere l’intero file riga per riga, ma questo può essere inefficiente per file di grandi dimensioni. Le collezioni e i flussi equenziati vengono in soccorso.
Coda di attività prioritaria con aggiornamenti dinamici
Si consideri un sistema di pianificazione delle attività in cui le attività arrivano con priorità diverse. È necessario mantenere una coda che dia priorità alle attività in base alla loro urgenza, consentendo al contempo aggiornamenti dinamici (inserimento e rimozione) delle attività.
Rischi e considerazioni sull’introduzione di Sequenced Collections
Sebbene le collezioni sequenziate offrano vantaggi significativi, la loro introduzione non è stata priva di sfide. Ecco una panoramica dei principali rischi e presupposti presi in considerazione dagli sviluppatori.
Method Name Collisions
L’aggiunta di nuovi metodi in alto nella gerarchia ereditaria può causare conflitti con i nomi dei metodi esistenti nelle sottoclassi. Metodi come reversed()
e getFirst()
possono potenzialmente entrare in conflitto con le implementazioni esistenti, causando un comportamento inaspettato.
Sovrascritture covarianti e incompatibilità binaria
Un problema particolare era rappresentato dalle sovrascritture covarianti per il metodo reversed()
sia su List
e Deque
. Se da un lato questo approccio garantisce la sicurezza dei tipi, dall’altro introduce incompatibilità di tipo sorgente e binario con le collezioni esistenti che implementano entrambe le interfacce. Questo potrebbe potenzialmente interrompere il codice che si basa su queste collezioni.
Impatto sulle implementazioni esistenti
Sono stati colpiti due esempi nel JDK: LinkedList
e una classe interna sun.awt.util.IdentityLinkedList
.
LinkedList
è stato risolto introducendo un nuovoreversed()
specifico perLinkedList
.- La classe interna
IdentityLinkedList
, ritenuta non necessaria, è stata eliminata.
Bilanciare rischi e benefici
Una proposta precedente considerava le sovrascritture covarianti per i metodi keySet()
, values()
e entrySet()
di SequencedMap
. Tuttavia, questo approccio comportava un elevato rischio di rottura delle sottoclassi esistenti, a causa del significativo cambiamento di comportamento.
Di conseguenza, gli sviluppatori hanno optato per un approccio più sicuro:
- sono stati aggiunti a
SequencedMap
nuovi metodi:sequencedKeySet()
,sequencedValues()
, esequencedEntrySet()
.
In questo modo si è evitato di modificare i metodi esistenti e di ridurre al minimo le potenziali incompatibilità.
Conclusioni
Gli sviluppatori delle collezioni sequenziate hanno considerato attentamente i rischi potenziali e hanno preso decisioni informate per ridurre al minimo le interruzioni e mantenere la compatibilità con le basi di codice esistenti. Sebbene siano stati necessari alcuni aggiustamenti (come nel caso di LinkedList
), l’approccio complessivo ha garantito una transizione più agevole verso un modo più coerente di gestire le collezioni ordinate in Java.
Autore: Luigi Cerrato, Software Engineer @ Bitrock