Cosa c’è di nuovo in Java 17?

Il 14 settembre 2021 ha visto la pubblicazione della versione Long-Term-Support (LTS) di Java, ovvero la Java 17, comprensiva del relativo ambiente di runtime. Analizziamo le innovazioni introdotte da Java 17 e valutiamo la convenienza dell’aggiornamento.

Numerose applicazioni continuano a operare su versioni precedenti di Java, incluse le edizioni LTS Java 11 e Java 8.

Qual è la ragione per cui le aziende dovrebbero considerare l’adozione della versione più recente di Java? L’upgrade a Java 17 richiede un investimento di risorse, ma è fondamentale per sfruttare appieno le nuove potenzialità e funzionalità implementate all’interno della JVM (Java Virtual Machine).

Molte realtà aziendali stanno adottando immagini Docker, facilitando la transizione a Java 17 con il minimo dispendio di tempo e risorse. Gli sviluppatori hanno la possibilità di configurare le proprie pipeline CI/CD (Integrazione Continua/Distribuzione Continua) ed eseguirle all’interno di container Docker. Questo approccio non influisce sui team che utilizzano versioni precedenti di Java, poiché possono mantenere le loro vecchie immagini Docker.

Funzionalità Principali di JAVA 17

Ottimizzazione per macOS e AArch64

Un miglioramento cruciale della JVM in questa versione è il potenziamento del supporto per macOS su architettura AArch64, derivante dal JEP 391. Ciò include la compatibilità con la più recente serie di processori Apple (M1), introdotti con i computer di ultima generazione.

Nonostante ciò non rappresenti una problematica per gli utenti di tali piattaforme, poiché vari fornitori hanno già rilasciato versioni di JDK compatibili con questa architettura (persino a partire da Java 8), il sigillo di approvazione ufficiale è essenziale per assicurare la futura manutenzione e supporto della piattaforma. Il supporto per la piattaforma Linux/AArch64 era stato aggiunto in Java 9, mentre quello per Windows/AArch64 in Java 16.

Classi Sigillate

Le Classi Sigillate, una novità di Java 17, hanno concluso la fase di test e sono state ufficialmente integrate nel linguaggio e nella piattaforma. Questa caratteristica permette agli sviluppatori di specificare i sottotipi consentiti per un determinato tipo, prevenendo estensioni o implementazioni non desiderate.

Le classi sigillate permettono al compilatore di segnalare errori durante la compilazione, in caso di tentativi di conversione di un tipo non sigillato in un sottotipo non autorizzato. Inoltre, Java 17 offre una nuova pipeline di rendering per le applicazioni AWT/Swing su macOS, impiegando l’API Apple Metal al posto di OpenGL. Infine, sono state introdotte API migliorate per la generazione di numeri casuali.

Modifiche, Rimozioni e Restrizioni in Java 17

Java 17 include anche diverse modifiche, eliminazioni e nuove limitazioni.

Incapsulamento delle API Interne del JDK

Una novità rilevante è il completamento dell’incapsulamento delle API interne del JDK. Tale processo, iniziato in Java 9, avvertiva a runtime gli utenti che tentavano di aggirare le restrizioni sull’uso delle API interne mediante riflessione o metodi simili. In aggiunta, sono stati introdotti argomenti della riga di comando per regolare tale comportamento.

A partire da Java 9, sono state sviluppate diverse API per fornire un modo standardizzato per eseguire le attività più comuni; si incoraggia gli utenti a utilizzare queste API. Con Java 16, l’impostazione di default è cambiata dall’avviso alla disabilitazione dell’accesso, generando un’eccezione. Tuttavia, è ancora possibile modificare tale comportamento tramite argomenti della riga di comando.

In Java 17, l’argomento della riga di comando viene rimosso, rendendo impossibile disabilitare questa restrizione. Ciò implica che qualsiasi accesso non autorizzato alle API interne è ora bloccato.

Semantica in Virgola Mobile Sempre Rigorosa

Una “rimozione” ulteriore è la reintroduzione della semantica in virgola mobile Always-Strict. Java 1.2 introdusse modifiche alla semantica predefinita in virgola mobile, permettendo alla JVM di sacrificare una piccola quantità di precisione per ottimizzare le prestazioni. La parola chiave strictfp veniva usata per classi e metodi in cui era necessaria una semantica rigorosa. Con l’avvento di nuovi set di istruzioni CPU, l’utilizzo della semantica rigorosa non comporta più costi di prestazioni, eliminando la necessità di semantiche predefinite o rigorose.

Java 17 elimina la precedente semantica predefinita, eseguendo tutte le operazioni in virgola mobile in modo rigoroso. Il termine strictfp rimane, ma non ha alcun effetto e genera un avviso durante la compilazione.

Compilazione Ahead-of-Time (AOT)

Java 9 aveva introdotto la compilazione AOT (Ahead-of-Time) come caratteristica sperimentale utilizzando il compilatore Graal, scritto in Java come codice JIT. Java 10 ha reso il compilatore Graal utilizzabile come compilatore JIT in OpenJDK, grazie all’interfaccia JVMCI. Il compilatore Graal ha compiuto notevoli progressi e possiede una propria JVM denominata GraalVM.

Attivazione RMI

L’attivazione RMI è stata eliminata in JEP 407, dopo essere stata rimossa da Java 8, resa obsoleta e segnata come requisito per la rimozione in Java 15. L’attivazione RMI consentiva l’abilitazione delle risorse su richiesta per gli oggetti distribuiti tramite RMI. Tuttavia, il suo utilizzo era limitato, e al momento sono disponibili alternative più valide. L’eliminazione dell’attivazione non ha effetto sul resto di RMI.

Rimozione dell’API Applet

L’API Applet è stata designata per la rimozione tramite JEP 398, dopo essere stata eliminata in Java 9. L’API Applet consentiva l’integrazione di controlli Java AWT/Swing in una pagina web all’interno di un browser. Nessun browser moderno supporta più tale funzionalità, rendendo le applet inaccessibili negli ultimi dieci anni.

Security Manager

La deprecazione più significativa è quella del Security Manager (JEP 411). Il Security Manager era in uso sin da Java 1.0 ed era progettato per limitare le operazioni che Java può eseguire sulla macchina locale, come l’accesso a reti, file e altre risorse. Cercava anche di proteggere il codice non affidabile, bloccando la riflessione e API specifiche.

La dismissione del Security Manager è iniziata in Java 12, con l’introduzione di un argomento della riga di comando per disabilitarne l’utilizzo a runtime. La modifica apportata in Java 17 comporta l’emissione di un avviso a runtime nel caso in cui si tenti di impostare un Security Manager, sia tramite riga di comando che dinamicamente a runtime.

Funzionalità Incubator e Preview

Molti si sono chiesti se Java 17 avrebbe introdotto funzionalità preview e incubator, data la sua natura di versione LTS. Java 17 presenta due moduli incubator e una funzionalità preview!

API Vettoriali

L’API vettoriale (JEP 414) si trova attualmente nella sua seconda fase di incubazione. Tale API permette agli sviluppatori di definire operazioni vettoriali, che il compilatore JIT convertirà nelle istruzioni vettoriali appropriate, compatibili con l’architettura della CPU su cui opera la JVM (come quelle dei set di istruzioni SSE o AVX).

In precedenza, gli sviluppatori dovevano ricorrere a funzioni scalari o a librerie native specifiche per la piattaforma. L’implementazione dell’API Vector in Java fornisce anche un meccanismo di fallback senza soluzione di continuità, una funzionalità difficile da ottenere nelle versioni precedenti.

La standardizzazione dell’API Vector permette alle classi interne del JDK di utilizzarla. Ad esempio, i metodi Java Arrays.mismatch() possono essere modificati per essere eseguiti in Java, eliminando la necessità di implementazioni specifiche per diverse piattaforme all’interno della JVM.

API di Memoria e Funzioni Esterne

Un’ulteriore funzionalità incubator è la Foreign Function & Memory API (JEP 412), un’evoluzione e fusione di due moduli incubator di Java 16: la Foreign Linker API (JEP 389) e la Foreign Memory API (JEP 393). Entrambe forniscono accesso a codice e memoria nativa tramite programmazione tipizzata staticamente in Java.

Pattern Matching per Switch

L’ultima funzionalità preview introdotta in Java 17 è l’inclusione del Pattern Matching per Switch (JEP 406). Questa caratteristica espande le espressioni e le istruzioni switch in base al tipo, in modo simile alla sintassi utilizzata tramite Pattern Matching (JEP 394), standardizzato in Java 16.

In passato, per eseguire azioni differenti in base alla natura dinamica di un oggetto, si doveva ricorrere a una catena if-else utilizzando instanceof, come nel seguente esempio:

String type(Object o) {
  if (o instanceof List) {
    return "Una lista di elementi.";
  }
  else if (o instanceof Map) {
    return "Una mappa con chiavi e valori.";
  }
  else if (o instanceof String) {
    return "Questa è una stringa.";
  }
  else {
    return "Qualcosa di diverso.";
  }
}

Combinando l’espressione switch con la nuova funzionalità di pattern matching per switch, il processo si semplifica notevolmente:

String type(Object o) {
  return switch (o) {
    case List l -> "Una lista di elementi.";
    case Map m -> "Una mappa con chiavi e valori.";
    case String s -> "Questa è una stringa.";
    default -> "Qualcosa di diverso.";
  };
}

Come si può notare, durante la verifica è possibile dichiarare una variabile. Analogamente alle altre variabili in Pattern, la corrispondenza dell’istanza indica che l’oggetto è stato verificato e convertito al tipo appropriato, ed è accessibile tramite la variabile all’interno della sua area di visibilità.

La funzionalità preview rappresenta un ulteriore passo verso il pattern matching; il passo successivo consisterà nell’inclusione della decostruzione di array e record.

Conviene Aggiornare a Java 17?

L’aggiornamento alla versione più recente è una pratica raccomandabile, ma non è strettamente necessario farlo immediatamente. I software e le librerie utilizzati potrebbero non essere stati ancora aggiornati per garantire la piena compatibilità con Java 17, quindi è opportuno attendere un periodo di transizione.

Se si opera con versioni LTS di Java come Java 8 o Java 11, Java 17 offre numerose migliorie nel linguaggio e nella JVM che giustificano l’aggiornamento. Essendo una versione di manutenzione a lungo termine, è molto probabile che l’ambiente di produzione sarà aggiornato a Java 17 nel prossimo futuro.

Per progetti totalmente nuovi o per la preparazione di progetti esistenti all’adozione di Java 17, la transizione anticipata si rivela la scelta più efficiente, riducendo i costi di migrazione. Inoltre, permette agli sviluppatori di utilizzare le ultime novità e ottimizzazioni, sia lato sviluppo che lato operativo.

È possibile beneficiare di numerosi miglioramenti avvenuti nel corso degli ultimi anni, come il supporto ottimizzato per l’esecuzione di container in Java e le nuove implementazioni dei Garbage Collector a bassa latenza.