Java 9 ha introdotto cambiamenti significativi alla struttura e all'architettura
del linguaggio. La politica di Oracle sembra
consolidarsi verso una direzione che porta al rilascio di
aggiornamenti del linguaggio più frequenti rispetto al passato.
Non
dobbiamo quindi stupirci dell'uscita della versione 10 a cosi breve
distanza nel tempo da quella precedente. In questo articolo
vogliamo illustrare le modifiche presenti nella nuova release che
riteniamo più interessanti.
Inferenza del tipo di dato dichiarato per una variabile locale
Il diamond operator, introdotto con la
versione Java 7, fa parte di quell'insieme di modifiche volte alla
riduzione della verbosità del linguaggio. Consideriamo il seguente
esempio pre Java 7:
List<String> list=new ArrayList<String>();
L'utilizzo del diamond operator consente di utilizzare un codice leggermente
più compatto:
List<String> list=new ArrayList<>();
La novità interessante di Java 10,in questo ambito, è l'introduzione
della parola chiave var
che consente l'inferenza del tipo di dato dichiarato per una variabile locale:
var list = new ArrayList<>();
var list = new ArrayList<String>();
Nel primo caso viene dedotto un ArrayList
di oggetti Object
mentre nel secondo caso un ArrayList
di oggetti String
. Possiamo
notare come il codice acquisti maggiore compattezza.
L'utilizzo della parola chiave var
è sottoposto
ad alcune restrizioni:
non può essere utilizzato nella dichiarazione di variabili senza inizializzazione o
con inizializzazione al valore null
:
var identificatore; // Non consentito
var identificatore1, identificatore2 = 0; // Non consentito
var identificatore = null; // Non consentito
Come ultima nota su questa nuova parola chiave, facciamo notare che il suo utilizzo è
possibile per i nomi di variabile, metodo e package ma non per un nome
di classe:
public class var {..} // Non consentito
La classe Optional
Rimanendo nel contesto di riduzione della verbosità, la classe Optional
viene dotata del metodo orElseThrow()
che consente di evitare
l'utilizzo del metodo orElseThrow(Exception)
quando l'eccezione
NoSuchElementException
è ciò di cui abbiamo bisogno:
Optional<String> optional = new Optional<String>();
var value = optional.orElseThrow();
invece di:
String value = optional.orElseThrow(NoSuchElementException::new);
API e metodi in Java 10
Nell'ambito delle API per la creazione di liste e mappe non
modificabili, i tipi List
, Set
e Map
sono stati dotati del metodo copyOf()
che
consente di ottenere una copia non modificabile di una lista, insieme o mappa
partendo da una già esistente:
var lista = new ArrayList<>;
lista.add("Str1");
lista.add("Str2");
var lista2 = lista.copyOf(lista);
lista2.add("Str3"); // produrrà un'eccezione
Alla sfera di queste modifiche appartiene l'introduzione dei
metodi toUnmodifiableList()
, toUnmodifiableSet()
e toUnmodifiableMap()
per la classe Collectors
del package Streams
:
var stream = lista.stream().collect(Collectors.toUnmodifiableList());
Miglioramenti delle prestazioni
Ci spostiamo adesso verso le migliorie che riguardano le performance
della JVM. Il codice prodotto per un enhanced for loop fino
alla versione 9 di Java può essere illustrato con il seguente esempio:
List<String> values = new ArrayList<>();
for(String value : values ) System.out.println(value);
che viene tradotto in:
List<String> values = new ArrayList<>();
String value;
for(Iterator<String> iterator = values.iterator();iterator.hasNext(); value = iterator.next())
System.out.println(value);
In Java 10 le variabili di tipo Iterator
sono dichiarate
all'esterno del ciclo ed inizializzate al valore null
appena
l'operazione è terminata. Il codice equivalente generato è quindi:
Iterator iterator = values.iterator();
String value;
for (; iterator.hasNext();) {
value = (String)iterator.next();
}
value = null;
iterator = null;
In questo modo le variabili non più utilizzate vengono
immediatamente rese eleggibili per la rimozione da parte del Garbage
Collector.
Il garbage collector G1 (Garbage-First) è progettato per dimensioni di heap superiori ai
4 GB e divide la dimensione
dello heap in diverse regioni. Il G1 utilizza una fase di marcatura
globale concorrente per determinare l'attività degli oggetti in tutto
l'heap. Al termine di questa fase il G1 conosce quali regioni sono
per la maggior parte vuote e rimuove prima gli oggetti non raggiungibili presenti
in queste regioni producendo, in genere, una grande quantità di spazio libero
(da questo il nome Garbage-First).
G1 utilizza anche
un modello di previsione delle pause in modo da soddisfare il tempo di
pausa definito dall'utente. Java 9 ha reso di default il G1 mentre la nuova versione Java 10
ha aggiunto al G1 un supporto completo di tipo Parallel Garbage Collector,
che utilizza più thread per analizzare lo spazio heap,
migliorando cosi la latenza nel caso peggiore.
Il numero di Threads in parallelo può essere controllato attraverso il parametro
XX:ParallelGCThreads
della JVM.
Docker
Concludiamo evidenziando le migliorie legate alla tecnologia Docker. Un sistema Docker utilizza il kernel di Linux per isolare
i processi in modo tale da poter essere eseguiti in maniera indipendente. Questa indipendenza è l'obiettivo
dei container: la capacità di eseguire più processi e applicazioni in modo separato
per sfruttare al meglio l'infrastruttura esistente, preservando, allo stesso tempo,
il livello di sicurezza che si avrebbe in presenza di sistemi separati.
La JVM di Java 10, quando in esecuzione su un sistema Linux, è in grado
di rilevare la sua esecuzione all'interno di un container Docker. La JVM
è dotata di tre nuove opzioni che forniscono agli utenti del container un migliore controllo
della memoria di sistema.
Sono stati inoltre corretti bug di collegamento fra
processi host e processi Java in esecuzione sul container Docker.