mar 19 marzo 2024 - Lo Sviluppatore  anno VI

JDK 9: Le novità per gli sviluppatori

Condividi

La più grande novità di JDK 9 è sicuramente il Java Platform Module System e l’introduzione del JDK modulare.
Tuttavia, ci sono molte altre nuove funzionalità nel che sono di particolare interesse per gli sviluppatori, ed è proprio su queste che ci concentreremo in questo articolo.

Cominciamo la carrellata delle novità con:

Factory Methods per le Collections

Le Collections sono strumenti molto utili per i programmatori perchè permettono di raggruppare  oggetti in maniera opportuna in modo tale da poter essere poi agevolmente manipolati . Parliamo delle ben note interfacce List, Set e Map e delle relative implementazioni che nella piattaforma Java rappresentano questo tipo di oggetti.

Fino alla versione 9 di Java non esisteva un metodo semplice per creare una Collection con dati predefiniti e ancora di più se si vuole rendere la collezione immutabile (cioè non è possibile aggiugere, togliere o modificare gli elementi) è necessario scrivere ancora altro codice. Per chiarire meglio facciamo un esempio:

Java 8:

List<Point> myList = new ArrayList<>();
myList.add(new Point(1, 1));
myList.add(new Point(2, 2));
myList.add(new Point(3, 3));
myList.add(new Point(4, 4));
myList = Collections.unmodifiableList(myList);

 

Java 9:

List<Point> list = List.of(new Point(1, 1), new Point(2, 2),
new Point(3, 3), new Point(4, 4));

E’ evidente la semplificazione della cosa usando Java 9.

Estensione e miglioramento della classe Optional

La classe Optional è stata introdotta in JDK 8 per ridurre il numero di posti nel codice in cui potrebbe avvenire un NullPointerException (Abbiamo scritto sugli Optional in questo articolo).
Il JDK 9 aggiunge quattro nuovi metodi alla suddetta classe:

  • ifPresent(Consumer action) : se c’è un valore presente, eseguire l’azione utilizzando il valore, altrimenti non fa nulla.
  • ifPresentOrElse(Consumer action, Runnable emptyAction): Simile a ifPresent, ma se non esiste alcun valore, esegue il comando emptyAction.
  • or(Supplier supplier): questo metodo è utile quando si si vuole essere sicuri di avere sempre un Optional. Questo metodo restituisce lo stesso Optional se c’è un valore
    presente; altrimenti restituisce un nuovo Optional prodotto dalla funzione Supplier.
  • stream (): Se c’è un valore restituisce uno Stream contenente il solo valore, altrimenti restituisce uno Stream vuoto.

Miglioramento della Streaming API

Già da Java 5 sono stati introdotti utility per semplificare la programmazione concorrente. Da lì in avanti sono stati fatti sempre dei miglioramenti nelle versioni successive, fino ad arrivare al JDK 7 con l’introduzione del framework fork/join fino ad arrivare a Java 8 in cui sono stati introdotti  gli stream paralleli.

Con il JDK 9 sono stati operati ancora ulteriori miglioramenti ,che possono essere, diciamo, raggruppati in due tipologie:

Il primo è il reactive streams publish-subscribe framework. Poter elaborare gli stream in maniera asincrona può servire ad evitare problemi quando siamo in presenza di grossi flussi di dati. In questi casi magari il processore non è in grado di gestire il carico e questo può causare dei blocchi del fornitore del flusso o un overflow del buffer.

I Reactive streams usano un modello publish-subscribe in cui chi elabora il flusso (il subscriber) si sottoscrive al fornitore di elementi (il publisher).
Per fare questo il fornitore sa sempre, in qualsiasi momento, quanti elementi può far passare nel flusso verso il sottoscrittore. Nel caso in cui il publisher
deve incrementare il flusso, si usano tecniche di buffering dei dati o può usare un fornitore alternativo.
(quest’ultimo è il tipo approccio usato nei microservices, cioè la scalabilità orizontale, in cui si creano nuove istanze dello stesso servizio in caso di aumento del carico).

Nel JDK 9 è stata creata una nuova classe, Flow, che racchiude diversi Interfacce: Flow.Processor, Flow.Subscriber, Flow.Publisher e Flow.Subscription.  Quest’ultima è usata per collegare un Publisher con un Subscriber.

L’interfaccia Subscription contiene due metodi:

  • cancel (): Questo metodo interrompe il flusso di messaggi verso il sottoscrittore.
  • request(long n): aggiungere n elementi al numero di elementi che il sottoscrittore
    è in grado di elaborare. Questo metodo può essere chiamato
    ripetutamente dal sottoscrittore se è in grado di gestire un flusso maggiore di dati.

Implementando l’interfaccia Processor, una classe può agire sia come publisher che come subscriber potendo attuare delle operazioni intermedie nello stream.

La seconda miglioria del JDK 9 nel settore della programmazione concorrente sono i miglioramenti apportati  alla classe CompletableFuture,  già introdotta con il JDK 8.  In questa classe sono stati aggiunti molti metodi per la gestione di eventi temporali. Questi metodi permettono di gestire i i time-out tramite metodi come completeOnTimeout ()delayedExecutor () e orTimeout () o ancora failFuture () e newIncompleteFuture ().

Milling Project Coin

Uno dei cambiamenti più significativi nel JDK 7 è stato il Progetto Coin, che consisteva in una serie di piccoli cambiamenti nel linguaggio atti a facilitare alcuni compiti in cui spesso si cimentano i programmatori Java; per dirne uno tra tanti, la possibilità di utilizzare le stringhe come costanti nelle istruzioni di switch-case.  Il JDK 9  continua su questa strada aggiungendo ulteriori modifiche alla sintassi del linguaggio, vediamo cosa:

  • E’ possibile utilizzare una variabile final  (una costante o una costante di fatto) all’interno di un blocco  try-with-resources senza essere riassegnata. Attualmente ogni risorsa usata all’interno del blocco try deve essere riassegnata anche se tale risorsa è stata già usata nel metodo; adesso è possibile usarla direttamente. Per essere più chiari:
    • Prima:  try (Resource r = risorsaGiaDefinita) { …
    • Ora:  try (risorsaGiaDefinita) { …
  • E’ possibile usare metodi private nelle interfacce. Già nel JDK 8 erano stati introdotti i default methods che permettevano di estendere le interfacce aggiungendo delle implementazione di metodi, senza “rompere” la retrocompatibilità col codice preesistente. Questo ha permesso di inserire dei “comportamenti” nelle interfacce dotando il linguaggio Java dell’ereditarietà (di comportamento) multipla, che prima non c’era. Su questa stessa linea è stato logico permettere anche l’inserimento di metodi statici. Nel JDK 9, con  l’introduzione dei metodi privati
    nelle interfacce, sarà possibile estrarre il codice comune dalle classi e incapsularlo nell’interfaccia.
  • E’ possibile usare l’operatore diamond <> nelle classi anonime. Una delle cose incluse nel jdK 8 e adesso nel jdk 9 è una migliore type inference. Questo si traduce ad esempio nella possibilità di passare ad un espressione lambda parametri senza tipo. Le modifiche apportate al compilatore java per dedurre automaticamente il tipo di una variabile ha portato anche alla suddetta feature di poter utilizzare l’operatore <> anche nelle classi anonime purchè il tipo sia deducibile.  Ad esempio:
    • List<String> myList = new ArrayList<>() {
      // Overridden methods
      };
  • L’underscore singolo (_) non si potrà più utilizzare come identificatore nei nomi delle variabili in quanto rappresenterà l’unico parametro di una espressione lambda che non viene usato nel corpo della funzione, ad esempio :
    _ -> getX()
  • È stato esteso l’utilizzo dell’annotazione @SafeVarargs ai metodi privati. Attualmente, questa annotazione può essere utilizzata solo su costruttori, metodi statici e metodi final, nessuno dei quali può essere sovrascritto. Poiché un metodo privato non può essere sovrascritto in una sottoclasse, è logico estendere l’uso di questa annotation anche ai metodi private.

Spin-Wait Hints (Suggerimenti sulle operazioni di spin)

Si tratta di un piccolo cambiamento perchè si tratta dell’aggiunta di un singolo metodo in più, ma trattandosi della classe Thread, questo cambiamento seppur piccolo è comunque significativo. Parliamo del metodo onSpinWait () che serve ad informare la JVM che il thread è attualmente in attesa in un ciclo di spin del processore. Se la JVM e l’hardware sono ottimizzati per questo tipo di operazioni, il suggerimento viene accettato altrimenti viene ignorato. Tipiche ottimizzazioni di questo tipo sono la riduzione della latenza tra l’avvio di un Thread ed un’altro e la riduzione del consumo energetico.

Variable Handles

Uno dei cambiamenti più significativi del JDK 9 derivati dalla modularizzazione delle librerie del core è l’incapsulamento delle API interne di default. Tra questi probabilmente il più conosciuto è sun.misc.Unsafe. Per rendere publiche queste API private nel JDK9 è stata introdotta la classe  VarHandle.

In java le variabili sono puntatori impliciti ad aree di memoria che contentgono un valore. Un Variable handles è una referenza tipata ad una variabile; per chi viene dal C, C++ sarebbe di fatto un puntatore ad un punatatore. Per ottenere un riferimento a un VarHandle, si utilizza la classe MethodHandle.Lookup. Con questa è possibile recuperare i riferimenti sia a varibili statiche che non statiche, compresi anche gli array. Una volta ottentuo un VarHandle è possibile eseguire operazioni di ordinamento a basso livello sulle aree di memoria riferite permettendo operazioni atomiche come possono essere operazione di compare e set, ma senza l’overhead introdotto nelle classi e metodi equivalenti del package java.util.concurrent.atomic.
È inoltre possibile utilizzare un VarHandle per riordinare le operazioni in memoria, come possono essere le operazioni lettura-scrittura o store-load. Questo potrebbe essere utile nel caso in cui non si vuole che il compilatore Java riordini secondo il suo criteri (spesso ai fini di ottimizzare le performance) le operazioni ma si vuole che le operazioni seguano un path di esecuzione ben preciso.

Process API

Il JDK 9 contiene miglioramenti alle classi Process e ProcessBuilder e introduce una nuova interfaccia ProcessHandle.

ProcessBuilder ora include il metodo startAsPipeline (),  che, come suggerisce il nome, crea una pipeline di processi stile Unix usando un elenco di ProcessBuilders. Qui ogni processo viene eseguito e il suo output è collegato all’input del processo successivo nella lista. Di seguito un esempio di utilizzo, dove viene creata una pipeline a partire da un comando ls della directory /tmp ottenemdo tramite il comando wc il numero di file presenti in tale directory:

ProcessBuilder ls = new ProcessBuilder()
.command("ls")
.directory(Paths.get("/tmp")
.toFile());
ProcessBuilder wc = new ProcessBuilder()
.command("wc", "-l")
.redirectOutput(Redirect.INHERIT);
List<Process> lsPipeWc = ProcessBuilder.startPipeline(asList(ls, wc));

L’interfaccia ProcessHandle in pratica permette il controllo di processi nativi fornendo metodi per recuperare le informazioni su un processo, come l’ID di un processo oltre agli id di eventuali processi figli. Esiste inoltre  un metodo statico che restituisce un Optional<ProcessHandle> dato un determinato ID di processo.

La sotto-interfaccia ProcessHandle.Info permette di avere ulteriori informazioni sui processi come ad esempio il comando eseguito per avviare il processo, quando è stato avviato il processo o ancora l’identità dell’utente che ha avviato il processo.

La classe Process è stata estesa con nuovi metodi utili a reperire informazioni sui processi nativi. Alcuni di questi sono presenti anche nella classe ProcessHandle e sono:

  • children() and descendants(): che restituiscono rispettivamente la lista dei processi figli e dipendenti.
  • getPid(): Restituisce l’id del processo
  • info(): Restituisce una “istantanea” delle informazioni come un’istanza di ProcessHandle.Info
  • onExit(): E’ un CompletableFuture che può essere usato per fare dei task quando il processo è terminato.
  • supportsNormalTermination(): Determina se il metodo destroy() termina il processo in maniera normale.
  • toHandle(): Restituisce il ProcessHandle del processo

Riferimenti

Java Magazine (Lug-Ago)

 

Lascia un commento

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.

Top