Points Clés
- Java 17, la dernière version de support à long terme de Java, a été mise à la disposition de la communauté Java en septembre 2021.
- Toutes les fonctionnalités examinées ici ont été officiellement ajoutées au langage, après avoir terminé leurs phases de "preview".
- Ces fonctionnalités sont purement syntaxiques, toutes étant de nouveaux types de classes et de mots-clés. Il y a beaucoup d'autres changements qui méritent d'être creusés !
- Bien que très avancé, Java 17 est une version complète et stable de Java, qui vaut vraiment la peine d'être apprise ou même de migrer vos applications vers cette version.
En septembre 2021, Oracle a diffusé Java 17, la nouvelle version avec support à long terme de Java. Si vous avez principalement travaillé en Java 8 ou Java 11, vous n'êtes peut-être pas au courant de certains des ajouts intéressants de Java 12 et des versions ultérieures.
Étant donné qu'il s'agit d'une version importante, j'ai pensé que ce serait bien de mettre en évidence certaines des nouvelles fonctionnalités qui ont personnellement éveillé mon intérêt !
Notez que la plupart des modifications apportées à Java sont d'abord « en preview », ce qui signifie qu'elles sont ajoutées à une version, mais qu'elles ne sont pas encore considérées comme terminées. Les gens sont encouragés à les expérimenter, mais découragés de les utiliser dans le code de production.
Toutes les fonctionnalités que j'ai identifiées ont été officiellement ajoutées à Java et ont dépassé leur phase de preview.
1 : les classes scellées
Les classes scellées, en preview dans Java 15 et officiellement ajouté à Java 17, sont un nouveau moyen d'appliquer les règles d'héritage. Lorsque vous ajoutez le mot clé sealed à la définition d'une classe ou d'une interface, vous ajoutez également une liste de classes autorisées à l'étendre ou à l'implémenter. Par exemple, si vous créez une classe définie comme :
public abstract sealed class Color permits Red, Blue, Yellow
Maintenant, seules les classes Red, Blue
et Yellow
peuvent l'étendre. Tout ce qui se trouve en dehors de la liste prédéfinie ne pourra pas être compilé.
Vous pouvez également renoncer entièrement au mot-clé permits
et conserver toutes les définitions des classes scellées dans le même fichier que la classe elle-même, comme suit :
public abstract sealed class Color {...}
... class Red extends Color {...}
... class Blue extends Color {...}
... class Yellow extends Color {...}
Notez que ces sous-classes ne sont pas imbriquées dans la classe scellée, mais viennent plutôt après la définition de classe. C'est fonctionnellement identique à l'utilisation du mot-clé permits
; Red, Blue
et Yellow
restent les seules classes pouvant étendre la classe Color
.
Alors, où les classes scellées sont-elles utiles ? Eh bien, en appliquant des règles sur l'héritage, vous appliquez également des règles sur l'encapsulation. Considérez le cas où vous construisez une bibliothèque logicielle et vous devez inclure la class
abstraite Color
. Vous savez ce qu'est Color
et ce qui doit l'étendre, mais s'il est déclaré comme public, qu'est-ce qui empêche le code extérieur de l'étendre ?
Que se passe-t-il si quelqu'un comprend mal son objectif et l'étend à Square ? Était-ce votre intention ? Ou vouliez-vous garder Color en interne ? Même dans ce cas, la visibilité au niveau du package n'empêche pas tous les problèmes. Que se passe-t-il si quelqu'un développe la bibliothèque plus tard ? Comment sauront-ils que vous n'envisagez Color que pour un petit sous-ensemble de classes ?
Les classes scellées protègent non seulement votre code des sources extérieures, mais elles communiquent l'intention à des personnes que vous ne rencontrerez peut-être jamais. Si une classe est scellée, vous dites que ce sont les classes destinées à l'étendre, et aucune autre. C'est une sorte de robustesse intéressante qui garantit que, des années plus tard, toute personne lisant votre code comprendra la rigueur de ce que vous avez écrit.
2 : Helpful Null Pointers
La fonctionnalité Helpful Null Pointers est une mise à jour intéressante - pas trop compliquée, mais tout de même un changement bienvenu dans la plateforme. Officiellement livrée en Java 14, Helpful Null Pointers améliorent la lisibilité des exceptions de type NullPointerException (NPE), en affichant le nom de l'appellant qui a levé l'exception, ainsi que le nom de la variable null. Par exemple, si vous appelez a.b.getName()
et que b n'est pas défini, la trace de pile l'exception indiquera que getName()
a échoué car b est null.
Maintenant, nous avons tous été là avant. Les NPE sont une exception très courante, et bien que la plupart du temps, il ne soit pas difficile de déterminer le coupable, de temps en temps, vous obtenez un cas où deux ou trois variables pourraient être responsables. Vous vous lancez donc dans le débogage, commencez à parcourir le code et vous avez peut-être du mal à reproduire le problème. Vous essayez donc de vous rappeler ce que vous avez fait pour créer le NPE en premier lieu.
Si seulement vous aviez pu recevoir les informations dès le départ, vous n'auriez pas à passer par tous ces tracas de débogage. C'est là que les Helpful Null Pointers brillent : plus besoin de deviner les NPE. A un moment clé, lorsque vous avez un scénario difficile à recréer, vous avez tout ce dont vous avez besoin pour résoudre le problème dès que l'exception se produit.
Cela pourrait être une bouée de sauvetage absolue!
3 : Switch Expressions
J'ai besoin que vous soyez avec moi pendant une seconde - Switch Expressions (en preview dans Java 12 et ajouté officiellement dans Java 14) sont un peu un mariage entre les instructions switch et les lambdas. Vraiment, la première fois que j'ai décrit les Switch Expressions à quelqu'un, j'ai dit qu'ils lambdafiaient les switchs. Pour un peu de contexte, jetez un œil à la syntaxe :
String adjacentColor = switch (color) {
case Blue, Green -> "yellow";
case Red, Purple -> "blue";
case Yellow, Orange -> "red";
default -> "Unknown Color";
};
Vous voyez ce que je veux dire ?
Une différence flagrante est l'absence de break. Les switch expressions continuent une tendance qu'Oracle a de réduire la verbosité de Java, c'est-à-dire qu'Oracle a vraiment détesté la façon dont la plupart des instructions switch sont écrites CASE BREAK CASE BREAK CASE BREAK etc.
Et honnêtement, ils avaient raison de détester ça, car c'était très sensible à l'erreur humaine. L'un d'entre nous peut-il dire qu'il n'a pas oublié une instruction break sur un switch, seulement pour être alerté lorsque notre code a paniqué au moment de l'exécution ? L'utilisation de Switch Expressions résout ce problème de manière amusante, en vous permettant de séparer simplement par des virgules toutes les valeurs qui tombent dans le même bloc. C'est vrai, plus de breaks ! Il se débrouille tout seul maintenant !
Une autre partie importante des Switch Expressions est le nouveau mot-clé yield
. Si l'un de vos cas utilise un bloc de code, yield
est utilisé comme instruction de retour de l'expression Switch. Par exemple, si nous modifions légèrement le bloc de code ci-dessus :
String adjacentColor = switch (color) {
case Blue, Green -> "yellow";
case Red, Purple -> "blue";
case Yellow, Orange -> "red";
default -> {
System.out.println("The color could not be found.");
yield "Unknown Color";
}
};
Dans le cas par défaut, la méthode System.out.println(
) sera exécutée et la variable adjacentColor
finira toujours par être "Unknown Color", car c'est ce que l'expression yield retourne.
Dans l'ensemble, les switch expressions sont des instructions switch beaucoup plus propres et condensées. Cela étant dit, ils ne remplacent pas les instructions switch, et les deux sont toujours disponibles et utilisables.
5 : Les blocs de texte
En preview pour la première fois dans Java 13, et ajouté officiellement dans Java 15, les Text Blocks sont un moyen de simplifier l'écriture de chaînes multilignes, capables d'interpréter de nouvelles lignes et de maintenir l'indentation sans avoir besoin de caractères d'échappement. Pour créer un bloc de texte, il vous suffit d'utiliser cette syntaxe :
String text = """Hello
World""";
Notez que cette valeur est toujours une chaîne, mais de nouvelles lignes et de nouvelles tabulations sont implicites. De même, si nous voulons utiliser des guillemets, aucun caractère d'échappement n'est nécessaire :
String text = """You can "quote" without complaints!"""; // You can "quote" without complaints!
La seule fois où vous auriez besoin d'un échappement par un barre oblique inverse (backslash), ce serait si vous essayiez d'écrire littéralement """ :
String text = """The only necessary escape is \""",
everything else is maintained.""";
En plus de cela, vous pouvez appeler la méthode format() de String directement sur ce que vous avez écrit, ce qui nous permet de remplacer facilement les informations à l'intérieur des blocs de texte par des valeurs dynamiques :
String name = "Chris";
String text = """My name is %s.""".format(name); // My name is Chris.
Les espaces de fin de ligne sont également retirés par ligne, sauf si vous le spécifiez avec '\s', un caractère d'échappement spécifique au bloc de texte :
String text1 = """No trailing spaces.
Trailing spaces. \s""";
Dans quelles situations utiliserions-nous les blocs de texte ? Eh bien, en plus d'être visuellement capable d'incorporer le formatage d'un grand bloc de mots, les blocs de texte facilitent infiniment le collage de portion de code dans les chaînes de caractères. L'indentation étant préservée, si vous deviez écrire un bloc de HTML ou de Python, ou de n'importe quel autre langage, vous pourriez l'écrire normalement et l'entourer de """. C'est tout ce dont vous avez besoin pour préserver le formatage. Vous pouvez même utiliser les blocs de texte pour écrire du JSON, et utiliser la méthode format()
pour remplacer facilement des valeurs.
En somme, un changement très pratique. Bien que les blocs de texte semblent être une petite fonctionnalité, ces fonctionnalités de qualité de vie s'additionnent sur le long terme.
5 : Les classes record
Les records, présentés en preview dans Java 14 et officiellement ajoutés dans Java 16, sont des classes de données uniquement qui gèrent tout le code passe-partout associé aux POJO. C'est-à-dire que si vous déclarez un Record comme suit :
public record Coord(int x, int y) {
}
Ensuite les méthodes equals()
et hashcode()
sont implémentées, toString()
renverra une chaîne propre de toutes les valeurs de Record, et plus important encore, x()
et y()
seront des méthodes qui retournent, respectivement, les valeurs de x
et y
. Pensez au POJO le plus laid que vous ayez jamais vu et imaginez le mettre en œuvre avec des Records. Cela aurait-il l'air bien plus beau ? À quel point cela serait-il plus cathartique ?
En plus de cela, les records sont à la fois finaux et immuables - pas d'extension d'un record, et une fois qu'un objet Record est créé, ses champs ne peuvent pas être modifiés. Vous êtes autorisé à déclarer des méthodes dans un record, à la fois non statiques et statiques :
public record Coord(int x, int y) {
public boolean isCenter() {
return x() == 0 && y() == 0;
}
public static boolean isCenter(Coord coord) {
return coord.x() == 0 && coord.y() == 0;
}
}
Et les Records peuvent avoir plusieurs constructeurs :
public record Coord(int x, int y) {
public Coord() {
this(0,0); // The default constructor is still implemented.
}
}
Remarque, lorsque vous déclarez un constructeur personnalisé dans le Record, il doit appeler le constructeur par défaut. Sinon, le record ne saurait que faire de ses valeurs. Si vous déclarez un constructeur qui correspond à la valeur par défaut, ce n'est pas grave, à condition d'initialiser également tous les champs de Record :
public record Coord(int x, int y) {
public Coord(int x, int y) {
this.x = x;
this.y = y;
} // Will replace the default constructor.
}
Il y a beaucoup de choses à dire concernant les Records. C'est un grand changement et ils peuvent être incroyablement utiles dans les bonnes situations. Je n'ai pas tout couvert ici, mais j'espère que cela vous donne l'essentiel de ce qu'ils sont capables de faire.
6 : Le Pattern Matching
Un autre développement dans la guerre d'Oracle contre la verbosité est le Pattern Matching. En preview dans Java 14 et Java 15, et officiellement publié dans Java 16, le Pattern Matching est un moyen de se débarrasser du casting inutile une fois qu'une condition instanceof
est remplie. Par exemple, nous connaissons tous cette situation :
if (o instanceof Car) {
System.out.println(((Car) o).getModel());
}
Ce qui, bien sûr, est nécessaire si vous souhaitez accéder aux méthodes de Car
de l'instance o. Cela étant dit, il est également vrai que sur la deuxième ligne, il ne fait aucun doute que o est une Voiture
- l'instanceof
l'a déjà confirmé. Donc, avec le Pattern Matching, un petit changement est apporté :
if (o instanceof Car c) {
System.out.println(c.getModel());
}
Maintenant, tout le détail de la conversion de l'objet est effectué par le compilateur. Apparemment mineur, mais il nettoie beaucoup de code passe-partout. Cela fonctionne également sur les branches conditionnelles, lorsque vous entrez dans une branche où il est évident de quel type est l'objet :
if (!(o instance of Car c)) {
System.out.println("This isn't a car at all!");
} else {
System.out.println(c.getModel());
}
Vous pouvez même utiliser le Pattern Matching sur la même ligne que l'instance elle-même :
public boolean isHonda(Object o) {
return o instanceof Car c && c.getModel().equals("Honda");
}
Bien qu'il ne soit pas aussi grandiose que certains des autres changements, le Pattern Matching est une simplification efficace de code pour un besoin courant.
Java continue de s'améliorer dans Java 17
Ce ne sont pas les seules mises à jour de Java 12 à Java 17, bien sûr, mais ce sont quelques-unes que j'ai trouvées intéressantes. Il faut beaucoup de courage pour exécuter votre grand projet sur la version la plus récente de Java, d'autant plus si vous migrez depuis Java 8.
Si les gens hésitent, cela a du sens. Cependant, même si vous n'avez pas l'intention de migrer, ou si cette mise à niveau particulière est prévue pour des années, il est toujours bon de suivre les nouvelles fonctionnalités intégrées dans la langage. J'espère avoir présenté ces concepts d'une manière qui les rende fidèles, de sorte que quiconque lisant puisse même commencer à réfléchir à des façons de les utiliser !
Java 17 est spécial : il s'agit de la prochaine release avec support à long terme, prenant le relais de Java 11, et sera probablement la version de Java la plus migrée dans les années à venir. Même si vous n'êtes pas prêt maintenant, il n'est jamais trop tôt pour commencer à apprendre, et lorsque vous vous retrouverez enfin sur un projet Java 17, vous serez déjà un développeur de pointe !
À propos de l'auteur
Christopher Bielak est un développeur Java chez Saggezza (une entreprise d'Infostretch), qui s'intéresse vivement au développement et à l'enseignement des langages logiciels. Il a une dizaine d'années d'expérience dans les banques et la finance, et bat souvent le rappel en faveur d'une évolution vers des technologies de pointe.