I. Java 12▲
I-A. Switch Expressions▲
C'est la principale nouvelle fonctionnalité de Java 12, les switch expressions permettent de définir des switchs en tant qu'expression pour en récupérer le résultat dans une variable, le switch ne va donc plus être juste une structure de contrôle (ensemble de if/else), mais permettre de calculer un résultat.
Les switch expressions ont été modifiées dans Java 13 et sont sorties de preview pour Java 14. Je vais ici vous présenter directement leur forme finale qui ne marche donc qu'avec Java 14.
Une nouvelle syntaxe des switchs a été ajoutée, plus pratique d'utilisation et plus concise, qui utilise l'opérateur arrow déjà utilisé dans les lambda : ->. On peut utiliser cette nouvelle syntaxe aussi bien dans un switch classique que dans un switch expression.
Voici un exemple de la nouvelle syntaxe qui utilise l'opérateur arrow, on peut noter que le break n'est plus nécessaire ici, cette nouvelle forme de switch ayant un break implicite :
2.
3.
4.
5.
6.
switch
(
day) {
case
MONDAY, FRIDAY, SUNDAY ->
System.out.println
(
6
);
case
TUESDAY ->
System.out.println
(
7
);
case
THURSDAY, SATURDAY ->
System.out.println
(
8
);
case
WEDNESDAY ->
System.out.println
(
9
);
}
Voici un exemple de switch expression qui permet de calculer un entier représentant la longueur du nom du jour, notez la présence du ; en fin d'expression qui indique bien que nous ne sommes plus ici dans une structure de contrôle, mais bien une expression :
2.
3.
4.
5.
6.
int
numLetters =
switch
(
day) {
case
MONDAY, FRIDAY, SUNDAY ->
6
;
case
TUESDAY ->
7
;
case
THURSDAY, SATURDAY ->
8
;
case
WEDNESDAY ->
9
;
}
;
Dans cet exemple, chaque case étant sur une ligne, on a un retour implicite de la variable (ici un entier littéral). Si ce retour n'était pas implicite on devrait utiliser le nouveau mot clé yield introduit en Java 13 pour retourner cette valeur en lieu et place d'un return classique.
2.
3.
4.
5.
6.
7.
8.
9.
int
j =
switch
(
day) {
case
MONDAY ->
0
;
case
TUESDAY ->
1
;
default
->
{
int
k =
day.toString
(
).length
(
);
int
result =
f
(
k);
yield result;
}
}
;
I-B. Divers▲
Il y a eu quelques ajouts à des API existantes :
- String::align et String::indent : permettent d'aligner une String multiligne ou de l'indenter ;
- String::transform : crée une nouvelle String par transformation de la première via une lambda fonction ;
- Files::isSameFile : permet de comparer deux fichiers pour savoir s'ils sont identiques (même contenu) ;
- Collectors::teeing : crée un collecteur qui est la composition de deux autres.
Une nouvelle API a vu le jour : java.text.CompactNumberFormat, permet de formater des nombres dans une forme compacte telle que définie par la norme LDML : 1000 -> 1K, 1000000 -> 1M…
I-C. Shenandoah GC▲
Le petit nouveau Garbage Collector (GC) sorti avec Java 12 : Shenandoah. Développé par RedHat et déjà inclus depuis plusieurs mois dans leur JVM, il est intégré en tant que fonctionnalité expérimentale dans Java 12.
Comme ZGC, c'est un GC qui est concurrent à l'application et qui promet des pauses minimales (de l'ordre de la milliseconde) pour des tas (heaps) de très grande taille (plusieurs centaines de Go). Il vise les tas (heaps) de grande taille et les machines à plusieurs cœurs.
Pour l'activer, utilisez les arguments de JVM au lancement comme suit :
-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
Si vous désirez en savoir plus sur ce nouveau GC, vous pouvez vous rendre sur son wiki. Ou regarder ce talk donné à Devoxx par Aleksey Shipilëv : https://vimeo.com/289626122.
I-D. Plus d'information▲
La liste des JEP (JDK Enhancement Proposal) de Java 12 : http://openjdk.java.net/projects/jdk/12/.
L'article de blog duquel est tiré cette section : Java 12 quoi de neuf ?
II. Java 13▲
II-A. Text Blocks▲
C'est LA grande nouveauté de la version 13 de Java, la possibilité d'écrire des Text Blocks : un nouveau type de String Literal qui permet d'écrire une String écrite sur plusieurs lignes.
Les Text Blocks n'apportent pas énormément de nouvelles fonctionnalités (ce ne sont pas des Raw String, il n'y a pas d'interpolation de chaîne ou autre), ils permettent juste d'écrire des Strings sur plusieurs lignes, et gèrent automatiquement l'indentation pour nous.
Au lieu d'utiliser un seul caractère d'échappement, ils en utilisent une séquence : """
2.
3.
4.
System.out.println
(
"""
Hello,
multiline
text blocks!"""
)
En parlant d'indentation, c'est bien ce qui est le plus spécifique dans cette nouvelle fonctionnalité ; un algorithme un peu complexe a été implémenté pour conserver l'indentation telle que le développeur avait l'intention de la définir.
Concrètement, l'indentation est faite en supprimant l'indentation avant la première lettre de chaque ligne (donc ici on supprime l'indentation avant le H de Hello, avant le m de multiline et avant le t de text), c'est ce qu'on appelle l'indentation accidentelle. Dans le cas où le premier caractère serait un espace, il est conservé.
Les règles principales des Text Blocks sont :
- commence par """ et un retour à la ligne ;
- suppression de l'indentation accidentelle et du premier retour à la ligne ;
- conservation du reste de l'indentation ;
- termine par """ sans retour à la ligne préalable. S'il y en a un, il sera ajouté à la fin de la string !
- s'il y a un retour à la ligne en fin de Text Block, sa position définira l'indentation accidentelle à la place de la première lettre du Text Block ;
- on peut utiliser une double-quote à l'intérieur d'un Text Block.
Pour utiliser les Text Blocks, il faut ajouter l'option -enable-preview à votre ligne de commande, car c'est pour l'instant une fonctionnalité en preview.
Pour l'implémentation des Text Blocks, des nouvelles méthodes ont été ajoutées à la classe String :
- String::formatted ;
- String::stripIndent ;
- String::translateEscapes.
Plus d'info dans l'article très complet de Nicolai Parlog sur le sujet : https://blog.codefx.org/java/text-blocks/.
Vous pouvez aussi lire le Programmer's Guide To Text Blocks par Jim Laskey et Stuart Marks.
II-B. Plus d'information▲
La liste des JEP de Java 13 : http://openjdk.java.net/projects/jdk/13/.
L'article de blog duquel est tiré cette section : Java 13 quoi de neuf ?
III. Java 14▲
III-A. Amélioration des Text Blocks▲
Text Blocks : la possibilité d'écrire des String Literals sur plusieurs lignes est toujours en preview avec l'ajout de deux nouvelles escape sequences : \ et \s.
\ permet de mettre sur plusieurs lignes une String qui doit être sur une seule (comme en shell).
2.
3.
4.
5.
6.
String text =
"""
Lorem
\
ipsum
\
dolor
\
"""
;
System.out.println
(
text); // Lorem ipsum dolor
\s permet d'ajouter un espace (\u0020) en fin de ligne qui serait normalement supprimé lors de l'indentation automatique des Text Blocks.
2.
3.
4.
5.
String colors =
"""
red
\s
green
\s
blue
\s
"""
;
IV. Des NullPointerExceptions plus utiles▲
Les NullPointerExceptions (NPE) sont monnaie courante en Java, et le message d'erreur est bien souvent peu utile, car pointe uniquement la ligne à laquelle l'exception arrive, et pas exactement quelle instruction/portion de code a généré cette exception.
SAP a implémenté dans sa JVM (depuis 2006 !) une version améliorée des messages des NPE. Fort de cette expérience, l'implémentation en a été revue dans OpenJDK, à mon grand regret, il faut ajouter une option au démarrage de la JVM, -XX:+ShowCodeDetailsInExceptionMessages, pour activer cette fonctionnalité fort utile.
Pour comparaison, voici les messages d'erreurs standard pour des NullPointerException.
On peut remarquer qu'on ne distingue pas quel objet est nul : a ? a.s ?
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
$
jshell
|
Welcome to JShell -- Version 14
-ea
|
For
an introduction type: /help intro
jshell>
public class A { public String s;}
|
created class A
jshell>
A a;
a ==>
null
jshell>
a.s.toString
(
)
|
Exception java.lang.NullPointerException
|
at (
#3:1)
jshell>
a.s =
"toto"
;
|
Exception java.lang.NullPointerException
|
at (
#4:1)
jshell>
a =
new A
(
);
a ==>
A@3f8f9dd6
jshell>
a.s.toString
(
)
|
Exception java.lang.NullPointerException
|
at (
#6:1)
Exécuter les mêmes lignes de code, mais en activant les messages d'erreur utiles pour les NPE va nous donner précisément quel objet est nul et quelle opération a généré une NPE sur cet objet (read, write, appel de méthode).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
$
jshell -R-XX:+ShowCodeDetailsInExceptionMessages
|
Welcome to JShell -- Version 14
-ea
|
For
an introduction type: /help intro
jshell>
public class A { public String s;}
|
created class A
jshell>
A a;
a ==>
null
jshell>
a.s.toString
(
)
|
Exception java.lang.NullPointerException: Cannot read field "s"
because "REPL.
$JShell$12
.a"
is null
|
at (
#3:1)
jshell>
a.s =
"toto"
;
|
Exception java.lang.NullPointerException: Cannot assign field "s"
because "REPL.
$JShell$12
.a"
is null
|
at (
#4:1)
jshell>
a =
new A
(
);
a ==>
A@3f8f9dd6
jshell>
a.s.toString
(
)
|
Exception java.lang.NullPointerException: Cannot invoke "String.toString()"
because "REPL.
$JShell$12
.a.s"
is null
|
at (
#6:1)
IV-A. Records▲
En citant la JEP : Records provide a compact syntax for declaring classes which are transparent holders for shallowly immutable data.
C'est un nouveau type Java (comme class
et enum
), son but est d'être un conteneur de données. Les Records sont implicitement « final
» et ne peuvent être « abstract
».
Les Records fournissent une implémentation par défaut pour du code boilerplate que vous auriez sinon généré via votre IDE.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
jshell>
record Point
(
int x, int y) { }
jshell>
Point p =
new Point
(
); // all fields need to be initialized at construction time
|
Error:
|
constructor Point in
record Point cannot be applied to given types;
|
required: int,int
|
found: no arguments
|
reason: actual and formal argument lists differ in
length
|
Point p =
new Point
(
);
|
^---------^
jshell>
Point p =
new Point
(
1
, 1
);
p ==>
Point[x
=
1
, y
=
1
]
Tous les Records ont des accesseurs publics (mais les champs sont privés) et une méthode « toString ».
2.
3.
4.
5.
6.
7.
8.
9.
jshell>
p.x //field is private
|
Error:
|
x has private access in
Point
|
p.x
|
^-^
jshell>
p.x
(
); //public accessor
$8
==>
1
jshell>
p.toString
(
); //default toString
(
)
$9
==>
"Point[x=1, y=1]"
Tous les Records ont les méthodes « equals » et « hashCode » dont les implémentations sont basées sur le type du Record et son état (ses champs donc).
2.
3.
4.
5.
6.
jshell>
Point other =
new Point
(
1
,1
);
other ==>
Point[x
=
1
, y
=
1
]
jshell>
other.equals
(
p);
$11
==>
true
jshell>
other ==
p
$12
==>
false
Vous pouvez redéfinir les méthodes et constructeurs par défaut d'un Record.
Quand vous redéfinissez le constructeur par défaut, il n'y a pas besoin de répéter l'initialisation des champs et vous pouvez directement accéder aux champs du Record.
2.
3.
4.
5.
6.
record
Range
(
int
lo, int
hi) {
public
Range {
if
(
lo >
hi) /* referring here to the implicit constructor parameters */
throw
new
IllegalArgumentException
(
String.format
(
"(%d,%d)"
, lo, hi));
}
}
IV-B. Pattern Matching pour instanceof▲
Chaque développeur a déjà écrit du code qui ressemble à ça avec l'opérateur instanceof
:
2.
3.
4.
if
(
obj instanceof
Integer) {
int
intValue =
((
Integer) obj).intValue
(
);
// use intValue
}
Le cast après instanceof
semble superflu, car on vient de tester le type de l'objet. Et c'est là qu'entre en scène le pattern matching, il va permettre de vérifier qu'un objet est d'un type précis (comme instanceof
le fait) et « extraire » la « forme » de l'objet dans une nouvelle variable. On va donc pouvoir remplacer le code précédent par celui-ci :
if
(
obj instanceof
Integer intValue) {
// use intValue
}
Plus besoin de cast, et on assigne à une variable locale au bloc qui va permettre d'utiliser directement l'objet via son type vérifié par l'opérateur instanceof
.
Pour utiliser le pattern matching, il faut ajouter l'option --enable-preview à votre ligne de commande, car c'est pour l'instant une fonctionnalité en preview.
Voici un exemple un peu plus complet via JShell.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
$
jshell --enable-preview
|
Welcome to JShell -- Version 14
-ea
|
For
an introduction type: /help intro
jshell>
public void print
(
Object o) {
...>
if
(
o instanceof String s) System.out.println
(
"String =>"
+ s);
...>
if
(
o instanceof Integer i) System.out.println
(
"Integer =>"
+ i);
...>
}
|
created method print
(
Object)
jshell>
print
(
1
)
Integer =>
1
jshell>
print
(
"toto"
)
String =>
toto
Brian Goetz a écrit un article plus vaste sur le Pattern Matching qui montre ce qui pourrait arriver dans les prochaines versions de Java sur le sujet : https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html.
IV-C. Divers▲
Il y a eu quelques ajouts à des API existantes :
- StrictMath::decrementExact ;
- StrictMath::incrementExact ;
- StrictMath::negateExact().
Une nouvelle annotation, @Serial
, a été créée. Elle permet de marquer des champs/méthodes comme relatifs à la sérialisation.
Comme le protocole de sérialisation est basé sur des champs et méthodes standard (serialVersionUid, readObject…) et pas un mécanisme ancré dans le JDK, le compilateur ne pouvait pas valider la signature de ces champs et méthodes. En annotant avec @Serial
le compilateur pourra alors en vérifier la bonne signature.
IV-D. Packaging Tool▲
jpackage est un outil permettant de packager votre application dans un format natif à votre OS (attention, c'est le format de packaging qui est natif par votre application, cela n'a rien à voir avec GraalVM native image).
Mon OS étant Ubuntu, jpackage va me permettre de packager mon application au format .deb. Je vais prendre comme exemple l'application getting-started de Quarkus dont j'ai packagé le jar via Maven. En effet, jpackage nécessite un jar ou un ensemble de .class pour pouvoir réaliser un package de votre application.
Tout d'abord, il faut utiliser jpackage pour générer un package. Il faut lui passer le répertoire de votre application (-input) et son main (ici -main-jar pour préciser le jar contenant le main).
$
jpackage --name getting-started --input target --main-jar getting-started-1
.0
-SNAPSHOT-runner.jar
WARNING: Using incubator modules: jdk.incubator.jpackage
Ensuite, je peux utiliser dpkg pour installer ce package sur mon OS.
2.
3.
4.
5.
6.
$
sudo dpkg -i getting-started_1.0
-1_amd64.deb
Sélection du paquet getting-started précédemment désélectionné.
(
Lecture de la base de données... 256348
fichiers et répertoires déjà installés.)
Préparation du dépaquetage de getting-started_1.0
-1_amd64.deb ...
Dépaquetage de getting-started (
1
.0
-1
) ...
Paramétrage de getting-started (
1
.0
-1
) ...
L'application sera installée dans /opt/getting-started. Pour la lancer, il y a un exécutable dans le répertoire bin.
2.
3.
4.
$
/opt/getting-started/bin/getting-started
2019
-12
-31
17
:17
:25
,933
INFO [io.quarkus] (
main) getting-started 1
.0
-SNAPSHOT (
running on Quarkus 1
.0
.1
.Final) started in
1
.130s. Listening on: http://0
.0
.0
.0
:8080
2019
-12
-31
17
:17
:25
,937
INFO [io.quarkus] (
main) Profile prod activated.
2019
-12
-31
17
:17
:25
,937
INFO [io.quarkus] (
main) Installed features: [agroal, cdi, resteasy]
Les fichiers de notre application se retrouvent dans /opt/getting-started/lib/app/, d'autres répertoires contiennent les fichiers propres à jpackage.
Pour finir, nous pouvons désinstaller le package via la commande dpkg
$
sudo dpkg -r getting-started
(
Lecture de la base de données... 256753
fichiers et répertoires déjà installés.)
Suppression de getting-started (
1
.0
-1
) ...
IV-E. Suppression du Concurrent Mark Sweep (CMS) GC▲
Après avoir été déprécié avec Java 9, le Garbage Collector CMS est finalement supprimé avec Java 14, personne dans la communauté n'ayant voulu le maintenir.
CMS était un algorithme de GC concurrent très performant, ciblant les tas (heaps) de taille moyenne et hautement configurable. Il souffrait du manque de phase de compaction et Oracle voulant investir de plus en plus dans G1 et ZGC ne désirait plus maintenir un algorithme concurrent de plus.
Jusqu'il y a peu, c'était un des algorithmes les plus performants pour les tas (heaps) de moyennes tailles, et bien souvent on n'arrivait pas à configurer G1 pour avoir des performances aussi hautes. Espérons qu'avec les nouvelles optimisations réalisées dans G1 dans les dernières versions de Java il arrive désormais à tenir la tête à CMS… ou alors il y a toujours ZGC et Shenandoah…
IV-F. Plus d'information▲
La liste des JEP de Java 14 : http://openjdk.java.net/projects/jdk/14/.
L'article de blog duquel est tiré cette section : Java 14 quoi de neuf ?
V. Conclusion▲
Grâce au rythme soutenu d'une release tous les six mois, Java continue à avancer et à embrasser les techniques de développement moderne.
Les apports dans ces versions le rendent attirant pour le développement de nouvelles applications et Java 14 sera, à partir d'aujourd'hui, la version de Java que j'utiliserai ;).
C'est aussi la version pour laquelle je formerai les gens via la formation Modern Java de Zenika (mal nommée Migration Java 12, mais qui est mise à jour à chaque nouvelle version de Java).
Et le mieux c'est qu'il y a encore pas mal de nouvelles fonctionnalités qui devraient arriver dans les prochaines versions : encore plus de pattern matching avec les patterns de déconstruction, les Fibers (project Loom) – des Threads légères dans la JVM, les Inline types (projet Valhalla), Vector API (projet Panama)…
VI. Remerciements▲
Cet article a été publié avec l'aimable autorisation de Loïc Mathieu. L'article original (De Java 12 à Java 14) peut être consulté sur le blog/site de Zenika.
Nous tenons à remercier Claude Leloup pour sa relecture orthographique attentive de cet article puis Winjerome pour la mise au gabarit.