Scroll Top
Via Antonio Amato, 20/22 84131 Salerno (SA)

Da Java 11 a Java 17: le novità più importanti

Java11-Java17_novità

(articolo redatto da Claudio De Sio Cesari)

Di seguito presenteremo le più importanti novità introdotte nel linguaggio a partire dalla versione 12 alla versione 17.

Questo articolo è impostato solo su semplici esempi, accompagnati da altrettanto semplici descrizioni. Per ogni argomento presentato, però, viene linkato un altro articolo di approfondimento. Inoltre verranno trattate solo le novità più rilevanti che riguardano il linguaggio e che quindi impattano sul lavoro quotidiano dello sviluppatore.

Non tratteremo le novità relative a: tool, garbage collectors, librerie, migrazioni, implementazioni e deprecazioni della piattaforma Java.

Switch Expressions

Dalla versione 14 il costrutto switch è stato rivisto per essere usato come espressione (ovvero come istruzione che ritorna un valore) ed è stato introdotta una nuova sintassi, basata sull’operatore freccia ->.
Per esempio oggi possiamo scrivere codice come il seguente:

Da Java 11 a Java 17

Possiamo osservare che il costrutto ora ritorna un valore (switch expression) che viene immagazzinato nella variabile season. Inoltre la sintassi per ritornare tale valore si basa sull’operatore freccia, che ci permette di poter evitare l’uso della parola chiave break, e di utilizzare la controversa tecnica del fall-through.

In realtà ci sono tante altre cose da dire riguardo il nuovo switch. Per esempio, che la sintassi basata sull’operatore freccia è utilizzabile anche con lo switch nella versione tradizionale (il costrutto non è un’espressione e non ritorna un valore). Poi è stata introdotta una nuova parola chiave contestuale: yield. Inoltre, grazie alla proprietà nota come exhaustiveness, il compilatore è capace di comprendere se abbiamo coperto tutti i casi. Infatti nel nostro esempio la clausola default non si deve usare.

Per tutti i dettagli potete consultare questo articolo d’approfondimento.

Text Block

Nella versione 15 è stata ufficializzata una importante novità per gli sviluppatori: i text block. Quando abbiamo bisogno di formattare il contenuto di una stringa per favorirne la leggibilità, siamo costretti ad utilizzare concatenazioni e caratteri di escape. Questo è particolarmente vero quando usiamo stringhe che contengono istruzioni di altri linguaggi come JSON, XML o SQL. Per esempio, consideriamo la seguente stringa JSON:

Da Java 11 a Java 17

Oggi possiamo usare i Text Block che ci permettono di dichiarare stringhe che si estendono su più righe. Quindi possiamo riscrivere la stringa precedente con la seguente sintassi:

Da Java 11 a Java 17

Possiamo notare che i text block sono delimitati da una sequenza di tre virgolette, il che ci permette di utilizzare al suo interno virgolette singole senza utilizzare caratteri di escape. Ma i text block sono anche delle stringhe a tutti gli effetti e quindi possiamo utilizzare tutti i metodi della classe String (altri ne sono stati aggiunti appositamente per supportare i text block).

Anche per quanto riguarda i text block c’è tanto da approfondire. Si possono usare nuovi caratteri di escape, parametrizzare i text block, usare best practices e così via.

Per tutti i dettagli potete consultare questo articolo d’approfondimento.

Tipi Record

La versione 16 ha introdotto ufficialmente un nuovo tipo di Java che va ad aggiungere alle classi, le interfacce, le enumerazioni e alle annotazioni: i record.

Con un tipo record possiamo rappresentare contenitori di dati immutabili, senza creare una classe appositamente. La sintassi dei record aiuta gli sviluppatori a concentrarsi sulla progettazione di tali dati, senza perdersi nei dettagli implementativi.

La caratteristica più evidente di un record è indubbiamente la sinteticità. Supponiamo per esempio di voler rappresentare una classe Quadro i cui oggetti sono immutabili. Prima della comparsa dei tipi record in Java, dovevamo scrivere una classe come la seguente:

Da Java 11 a Java 17

Oggi invece possiamo semplicemente dichiarare il seguente record:

public record Quadro(String titolo, String autore, int prezzo) { }

Infatti tale record verrà trasformato dal compilatore in una classe come la precedente, con poche differenze: la classe generata dal compilatore estenderà la classe java.lang.Record e i metodi getter si chiameranno esattamente come i nomi delle variabili d’istanza (in questo caso titolo(), autore() e prezzo()). Quindi con una riga di codice possiamo definire l’equivalente classe immutabile generando automaticamente oltre ai metodi getter, i metodi toString(), equals(), hashcode(), il costruttore (detto costruttore canonico).

Per approfondire potete riferirvi all’articolo di approfondimento.

Sealed Classes

La versione 17 ha introdotto una nuova caratteristica nota con il nome Sealed Classes (che in italiano possiamo tradurre come classi sigillate), ma che in realtà riguarda anche le interfacce, e che quindi preferiamo chiamare Sealed Types (ovvero tipi sigillati). Ora possiamo dichiarare classi ed interfacce imponendo alcuni limiti sulla loro estensione/implementazione.

Prima dell’avvento di questa caratteristica, l’unico controllo che avevamo sull’estensibilità, consisteva nell’impedire ad una classe di essere estesa dichiarandola final (oppure dichiarando tutti i suoi costruttori privati).

Una classe dichiarata con il modificatore sealed invece deve dichiarare anche quali sono le sue uniche sottoclassi. Questo permette maggiore controllo sull’ereditarietà, aprendo la via ad altre importanti caratteristiche come il pattern matching per il costrutto switch come vedremo nell’ultimo paragrafo di questo articolo.

È stato introdotto il modificatore sealed che può essere utilizzato solo per classi ed interfacce, e la parola chiave permits che rappresenta una clausola per specificare le sottoclassi/sottointerfacce.

Quindi un tipo dichiarato sealed deve specificare anche da quali classi viene esteso/implementato, di solito specificando la clausola permits.

Consideriamo per esempio la seguente gerarchia:

Da Java 11 a Java 17

In pratica la classe DiscoOttico può essere estesa solo dalle classi CD e DVD. Per definire un’altra sottoclasse, bisognerà aggiungerla alla lista definita dalla clausola permits, altrimenti il codice non sarà compilabile. Questa caratteristica incoraggia a creare gerarchie semplici e coerenti, e permette a colpo d’occhio la visualizzazione delle sottoclassi leggendo la dichiarazione della superclasse. Notare che le sottoclassi di una classe sealed devono a loro volta essere dichiarate final, sealed oppure non-sealed (la terza parola chiave contestuale introdotta per il supporto a questa caratteristica).

Per approfondire potete riferirvi al relativo articolo di approfondimento.

Pattern matching per instanceof

Il pattern matching è una articolata caratteristica che Java sta introducendo gradualmente nel linguaggio, che ci permette tramite un predicato (test basato sul matching di un operando) di ottenere una o più variabili di binding, ovvero delle variabili il cui scope dipende dal predicato.

Il pattern matching per instanceof semplifica il tipico utilizzo di questo operatore.

Per esempio, supponiamo di avere una classe Punto che dichiara le variabili d’istanza x e y, potremmo definire il metodo equals nel seguente modo:

Da Java 11 a Java 17

Questo è un pattern ben conosciuto e molto utilizzato, ma è verboso, ripetitivo (la parola Punto ripetuto 3 volte in 2 righe), soggetto ad errori ed il cast è scontato.
Con il pattern matching per instanceof possiamo riscrivere il metodo equals in maniera meno verbosa e più robusta:

Da Java 11 a Java 17

Pattern matching per switch (Preview 17)

Java 17 ritocca nuovamente il costrutto switch che era già stato modificato dalla versione 14.

Con il pattern matching per switch, ora è possibile passare in input ad un costrutto switch un qualsiasi oggetto. In questo caso, le clausole case non coincideranno con delle costanti, bensì con dei tipi che sono compatibili gerarchicamente con il tipo specificato. Il controllo per eseguire il codice relativo ad una certa clausola case, quindi non sarà fatto verificando l’uguaglianza del parametro in input allo switch con una costante come è di solito avviene nel costrutto switch. Invece viene sfruttato l’operatore instanceof, che grazie al pattern matching definirà anche le variabili di binding per ogni case.

Per esempio, considerando le classi DiscoOttico, CD e DVD che abbiamo definito nel paragrafo delle sealed classes. Possiamo usare il pattern matching con il costrutto switch con la seguente sintassi:

Da Java 11 a Java 17

In base al tipo a cui punterà al runtime il parametro discoOttico, il flusso di esecuzione eseguirà il codice specificato da uno dei due case. Notare come vengano dichiarate le variabili di binding subito dopo il tipo, e come possano essere utilizzate solo all’interno del relativo codice.

Notare infine che non è necessario utilizzare la clausola default in casi come questi. Infatti l’utilizzo della classe astratta sealed DiscoOttico ci garantisce che come input questo switch può accettare solamente oggetti di tipo CD e DVD, e che quindi non è necessario aggiungere una clausola default perché tutti casi sono già stati coperti. Anche in questo caso la verbosità della sintassi del costrutto è stata notevolmente ridotta, mentre è aumentata la robustezza.

In realtà questa caratteristica è ancora in anteprima in Java 17 e in Java 18, e probabilmente verrà ufficializzata solo nella versione 20. Questo significa che per compilare un’applicazione che fa uso del pattern matching per switch, bisogna specificare determinati flag come descritto nell’articolo dedicato alle caratteristiche in anteprima.

Anche in questo caso potete approfondire l’argomento con l’articolo dedicato.

Conclusioni

In questo articolo abbiamo visto come Java 17 abbia introdotto diverse novità rispetto a Java 11, che stanno rivoluzionando il linguaggio.

La direzione dello sviluppo che ha impostato Oracle, ha come obiettivi principali quelli di rendere Java meno sempre più robusto, complesso, potente e meno verboso. E non è finita qui perché la pianificazione è molto lunga, e guidata dalle richieste della community. Nelle prossimi mesi potremo già iniziare a provare in anteprima altre interessantissime novità come i virtual thread e la decostruzione degli oggetti.

Insomma, il meglio deve ancora venire!


Se anche tu vuoi occuparti di progetti di sviluppo software di ultima generazione
dai un’occhiata alle nostre opportunità di lavoro e conosciamoci subito!