I. Le nouveau rythme des versions▲
Les architectes du langage Java ont fait un constat : le rythme des versions de Java jusqu'à la version 8 était d'une tous les 3 ans environ, ce faisant, on avait peu de versions avec à chaque fois beaucoup de fonctionnalités. C'était donc compliqué de livrer une nouvelle version de Java et compliqué pour les développeurs de faire la montée de version de Java pour leur application, au vu du nombre de changements inclus dans chaque nouvelle version. Comme une version avait un ensemble de fonctionnalités prévues, si une fonctionnalité prenait plus de temps à être développée, elle retardait la sortie de la nouvelle version.
De plus, ce rythme ne collait plus avec la réalité du monde de l'informatique où tout va de plus en plus vite ! Les architectes ont donc décidé de publier une version tous les 6 mois (donc un calendrier fixe), s'inspirant de ce que fait Ubuntu par exemple.
Dans chaque version, toutes les fonctionnalités prêtes sont incorporées, celles qui ne sont pas prêtes seront livrées dans la suivante (plus de version en retard).
Chaque version sera maintenue 6 mois par Oracle (libre à d'autres éditeurs de les supporter plus longtemps). Pour les entreprises qui ne peuvent ou ne veulent pas tenir le rythme, il y aura tous les trois ans une version dite LTS — Long Term Support — qui sera maintenue trois ans.
Le but de ceci : livrer de nouvelles fonctionnalités en continu (on parle alors de « release train »), forcer les utilisateurs à migrer et simplifier lesdites migrations, car le périmètre de chaque version est plus petit.
Mon avis sur la question : il faut vivre avec son temps. Tout le monde ou presque fait de l'intégration continue et/ou du déploiement continu, donc passer d'une version à l'autre de Java ne doit pas être un problème. Je conseille donc, à chaque nouveau projet, de passer sur la version de Java la plus récente. Ceux qui sont réfractaires au changement peuvent rester sur les LTS mais en tout cas, il faut dépasser Java 8 et embrasser le nouveau rythme des versions.
Posez-vous la question pour ceux qui font du JavaScript ou du TypeScript : quand migrez-vous ? Ubuntu et Angular font bien une version tous les 6 mois non ?
Donc, Oracle s'est juste aligné sur les bonnes pratiques du marché en termes de version.
Ce nouveau rythme a permis à Oracle de fournir dans le JDK, une implémentation de TLS 1.3 en quelques mois, ce qui aurait été impensable il y a quelques années.
II. Les changements en termes de distribution▲
Oracle fournit deux distributions de Java :
- OpenJDK : sous licence GPL v2 with ClassPath Exception, simple archive à décompresser : http://jdk.java.net/11/ c'est celle que je vous conseille ;
- Oracle : sous licence commerciale, avec support possible (qui commence à 25 $/mois/CPU core) et installeur pour les différentes plates-formes supportées : https://www.oracle.com/technetwork/java/javase/downloads/jdk11-downloads-5066655.html.
Ces deux distributions sont identiques en termes de fonctionnalités, Oracle ayant opensourcé les quelques fonctionnalités commerciales qui restaient dans Java (Java Flight Recording, Mission Control, CDS…).
D'autres vendeurs proposent leur propre distribution :
- RedHat via les repositories de leurs distributions : https://developers.redhat.com/products/openjdk/overview/ ;
- Azul qui a deux versions : Zulu (basé sur OpenJDK) qui est gratuite (avec support payant facultatif) et Zing qui est une JVM payante avec des composants spécifiques développés par Azul (G ) https://www.azul.com/ ;
- AdoptOpenJDK : https://adoptopenjdk.net/ ;
- …
Nous avons désormais le choix dans la distribution et dans le mode de support : communautaire ou payant (et fourni par plusieurs vendeurs différents !).
III. Les nouveautés de Java 9▲
III-A. La modularisation du JDK▲
Un système de module a été intégré à Java 9, le JDK en lui-même a été modularisé, ce qui permet de mettre des limites claires à chaque API qui déclare alors de quelles autres parties du JDK elle dépend et quels services elle expose. Le but de la modularisation est de permettre une évolution plus rapide du JDK, de pouvoir connaître plus facilement les dépendances entres les différentes parties du JDK et de pouvoir « cacher » les API internes du JDK. Cela ouvre aussi la porte à de nouvelles fonctionnalités telles que la possibilité de créer des bibliothèques Java natives, avec uniquement les parties du JDK utilisées par un programme, via l'outil jlink.
Bien que ce soit la principale nouvelle fonctionnalité de Java 9, ce n'est pas la seule et loin de là !
III-B. Un client HTTP/2 en stade expérimental▲
Ce nouveau client HTTP vient remplacer le très vieux HTTPUrlConnection intégré dans le JDK et quasiment jamais utilisé directement. Il implémente HTTP/2, propose une API facile d’accès et permet un mode asynchrone ou synchrone.
2.
3.
4.
5.
6.
7.
HttpClient.getDefault
(
)
.request
(
URI.create
(
"http://www.zenika.com"
))
.GET
(
)
.responseAsync
(
) // CompletableFuture
.thenAccept
(
httpResponse ->
System.out.println
(
httpResponse.body
(
HttpResponse.asString
(
)))
);
III-C. Une refonte de l'API Process via ProcessHandle▲
Gérer depuis java l'appel à un programme (process) externe a toujours été compliqué. Surtout si on veut faire des opérations qui semblent basiques, mais qui n'ont pas été prévues dans l'implémentation actuelle (récupérer le PID, killer un process, récupérer la ligne de commande…).
L'API Process a été grandement améliorée pour permettre toutes ces opérations, sur le process de la JVM (ProcessHandle.current
(
)), ou un process fils créé via Runtime.getRuntime
(
).exec
(
"your_cmd"
). De plus, comme toujours avec Java, c'est compatible avec les différents OS supportés par Java (et donc avec Windows ET Linux !).
En voici quelques exemples d'utilisations en Java 9 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
// Get PIDs of own processes
System.out.println
(
"Your pid is "
+
ProcessHandle.current
(
).getPid
(
));
//start a new process and get PID
Process p =
Runtime.getRuntime
(
).exec
(
"sleep 1h"
);
ProcessHandle h =
ProcessHandle.of
(
p.getPid
(
))
.orElseThrow
(
IllegalStateException::new
);
// Do things on exiting process : CompletableFuture !
h.onExit
(
).thenRun
(
(
) ->
System.out.println
(
"Sleeper exited"
) );
// Get info on process : return Optional!
System.out.printf
(
"[%d] %s - %s
\n
"
, h.getPid
(
), h.info
(
).user
(
).orElse
(
"unknown"
),
h.info
(
).commandLine
(
).orElse
(
"none"
));
// Kill a process
h.destroy
(
);
III-D. Méthode factory de création de collections▲
Ces nouvelles méthodes permettent de créer facilement des petites collections immuables.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
List<
Integer>
listOfNumbers =
List.of
(
1
, 2
, 3
, 4
, 5
);
Set<
Integer>
setOfNumbers =
Set.of
(
1
, 2
, 3
, 4
, 5
);
Map<
String, String>
mapOfString =
Map.of
(
"key1"
, "value1"
, "key2"
, "value2"
);
Map<
String, String>
moreMapOfString =
Map.ofEntries
(
Map.entry
(
"key1"
, "value1"
),
Map.entry
(
"key2"
, "value2"
),
Map.entry
(
"key1"
, "value3"
)
);
III-E. Une implémentation des Reactive Stream▲
La classe Flow propose trois interfaces pour implémenter vos stream réactives :
- Publisher : produit des messages que les subscriber vont consommer. La seule méthode est subscribe(Subscriber) ;
- Subscriber : souscrit à un publisher pour recevoir des messages (via la méthode
onNext
(
T)), des messages d'erreur (onError
(
Throwable)), ou un signal indiquant qu’il n'y aura plus de messages (onComplete
(
)). Avant toute chose, le publisher doit appeleronSubscription
(
Subscription) ; - Subscription : la connexion entre un publisher et un subscriber. Le subscriber va l'utiliser pour demander des messages (
request
(
long
)), ou pour rompre la connexion (cancel
(
)).
Un tutoriel est disponible ici : https://community.oracle.com/docs/DOC-1006738
III-F. jshell: The Java Shell (Read-Eval-Print Loop)▲
Dans le but de réduire le cérémonial nécessaire pour tester le langage Java, un shell (ou repl : read-eval-print-loop) a été introduit dans Java pour directement tester, depuis la ligne de commande, le langage Java.
Exemple ci-dessous.
III-G. Autres API▲
De nombreuses API existantes ont été améliorées par l'ajout de nouvelles méthodes, on peut citer :
- des ajouts à l'API de Stream :
iterate
(
),ofNullable
(
) et dans les Collectors :filtering
(
),flatMapping
(
) ; - des ajouts à Optional :
or
(
Supplier),ifPresent
(
Consumer),ifPresentOrElse
(
Consumer, Runnable),stream
(
) ; - la création de Stream à partir d'une LocalDate :
datesUntil
(
).Performance.
Chaque version de Java se veut plus performante et moins gourmande en mémoire. Java 9 apporte aussi son lot de nouveauté sur ces sujets :
- Improve Contended Locking : optimisation des monitors Java (optimisation des lock) ;
- Compact Strings : revue de l'implémentation des chaînes de caractères (String) en Java pour en proposer une version plus compacte en cas de String ISO-8859-1 (ou Latin-1). Les String Java sont stockées en UTF-16 par défaut : chaque caractère est alors stocké sur deux octets. À la création de la String, si elle est compatible ISO-8859-1, elle sera stockée de manière compacte avec chaque caractère sur un octet et non deux ;
- Indify String Concatenation : optimisation de la concaténation des chaînes de caractères en interne de la JVM ;
- passage du garbage collector par défaut du Parallel GC au G1 GC.
IV. Les nouveautés de Java 10▲
IV-A. Local-Variable Type Inference▲
C'est la nouvelle fonctionnalité de Java 10 : l'introduction du mot-clef var, permettant de remplacer le type d'une variable locale, quand celle-ci peut être inférée par le compilateur.
Java reste un langage statiquement typé, mais quand on déclare une variable puis on l'instancie sur la même ligne, la déclaration de type est redondante ! Le mot-clef var
va donc pouvoir remplacer le type.
À la compilation, var
sera remplacé par le type de la variable par le compilateur : il n'y a donc pas de changement au runtime.
En Java 9 :
MyComplexType obj =
new
MyComplexType
(
);
Map<
String,List<
MyComplexType>>
map =
new
HashMap<
String,List<
MyComplexType>>(
);
En Java 10 avec le mot-clef var :
var
obj =
new
MyComplexType
(
);
var
map =
new
HashMap<
String,List<
MyComplexType>>(
);
Stuart Marks a publié des modes d’emploi sur l'utilisation du mot-clef var
: http://openjdk.java.net/projects/amber/LVTIstyle.html
IV-B. Copy factory methods▲
L'API Collection a été enrichie de méthodes statiques permettant la copie de collections existantes : List.copyOf
(
), Set.copyOf
(
) et Map.copyOf
(
).
Dans les trois cas, la collection retournée sera une collection immuable. Si la collection existante était déjà une collection immuable, elle sera retournée directement (car il n'y a pas d'intérêt à créer une copie d'une collection immuable qui sera elle-même immuable…), sinon une nouvelle collection immuable sera créée.
IV-C. Collectors to unmodifiable List▲
Dans la même mouvance que le précédent changement, les collectors de l'API Stream ont été enrichis pour permettre la création de collections immuables :
toUnmodifiableList
(
)toUnmodifiableSet
(
)toUnmodifiableMap
(
keyFunc, valueFunc)toUnmodifiableMap
(
keyFunc, valueFunc, mergeFunc)
V. Les nouveautés de Java 11▲
V-A. HTTP Client (Standard)▲
Le nouveau client HTTP, introduit en expérimental dans Java 9, a été inclus en standard dans Java 11, dans son propre package java.net.http.
Son implémentation a été totalement revue, elle est basée sur une API totalement asynchrone.
V-B. Launch Single-File Source-Code Programs▲
Dans le but de réduire le cérémonial nécessaire pour utiliser Java, on peut désormais lancer un programme basique Java (écrit dans un seul fichier .java) directement, sans passer par l'étape compilation, via la ligne de commande java.
Une compilation du programme en mémoire sera alors réalisée avant son exécution.
V-C. Les nouveaux GCs▲
Deux nouveaux Garbage Collector (GC) ont été intégrés à Java :
- Epsilon : un GC qui ne nettoie rien ! Il alloue les objets jusqu'à ce qu'il ne puisse plus, puis arrête la JVM. Dédié à une utilisation expérimentale dans le domaine du développement des GC ;
- ZGC (Zero GC) : sorti des laboratoires de recherche d'Oracle, Oracle Labs. Ce nouveau GC vise des pauses de moins de 10ms pour des applications pouvant utiliser jusqu'à des To de mémoire. Ce n'est pas un remplaçant de G1 mais un GC dédié aux très très grosses heap.
En plus de ces deux GC, un grand travail a été réalisé sur G1, le GC par défaut depuis Java 9, pour le rendre plus performant et moins gourmand en mémoire.
Selon l'auteur d'une partie de ces changements, en passant de Java 8 à Java 11, on aurait des pauses 60 % plus basses « gratuitement » pour une utilisation mémoire grandement réduite.
V-D. Autres API▲
De nombreuses API existantes ont été améliorées par l'ajout de nouvelles méthodes, on peut citer :
- méthodes pour comparer CharSequence, StringBuilder et StringBuffer : ajout de
compareTo
(
) ; isEmpty
(
) : retournetrue
si la valeur n'est pas présente ;- méthodes à nio.file.Files pour lire/écrire depuis une String ;
not
(
) : négation d'un prédicat ;convert
(
Duration) : conversion d'une Duration vers une TimeUnit ;String
:
:repeat : créé une String qui est la répétition de la String sur laquelle la méthode est appelée ;String
:
:strip,String
:
:stripLeading,String
:
:stripTrailing : créé une String en retirant les espaces (tels que définis par isWhitespace) de la String sur laquelle la méthode est appelée ;String
:
:isBlank : retournetrue
si la String est vide ou ne contient que des espaces (tels que définis par isWhitespace) ;String
:
:lines : retourne une Stream avec les lignes de la String. Plus performant que split car réalise le split de manière lazy.
VI. Conclusion▲
Le nouveau rythme des versions nous force à aller de l'avant et c'est tant mieux ! De plus en plus d'organisations contribuent au développement de Java dans ses fonctionnalités, via la diffusion de distributions et en offrant du support.
L'écosystème s'élargit !
Pour tirer parti des nouvelles fonctionnalités et des optimisations en termes de performance et de sécurité, il nous faut migrer au fur et à mesure sur ces nouvelles versions.
Encore heureux pour nous, nos pipelines modernes de développement et nos pratiques d'industrialisation nous permettent de le faire en toute sérénité via les outils à notre disposition : usine de développement (CI/CD), automatisation de la production (ansible, docker…), orchestration de la production (kubernetes), monitoring (Prometheus, Istio…) etc.
Donc, n'ayons pas peur et passons à Java 11 (version OpenJDK bien sûr) !
Et pour ceux qui auraient peur dans l'avenir de Java et dans la pérennité de son modèle Open Source, je les invite à lire la réponse d'experts reconnus dans la communauté sur le sujet : Java Is Still Free.
VII. Remerciements▲
Cet article a été publié avec l'aimable autorisation de Loïc Mathieu. L'article original (Java.Next : De Java 8 à Java 11 : Nouveautés et changements pour les développeurs) peut être consulté sur le blog/site de Zenika.
Nous tenons à remercier Escartefigue pour sa relecture orthographique attentive de cet article puis Winjerome et Mickael Baron pour la mise au gabarit.