Grâce à son nouveau cycle de sortie (une nouvelle version majeure tous les six mois, dont une version bénéficiant du support à long terme tous les trois ans), Java évolue vite, très vite, permettant ainsi au langage de rattraper son retard face à NodeJS, Python et même face aux autres langages de la JVM (comme Kotlin ou Scala).
Pour rappel, voici le planning des versions de Java 11 à 17 :
Version | Date de sortie |
---|---|
Java 11 (LTS) | Septembre 2018 |
Java 12 | Mars 2019 |
Java 13 | Septembre 2019 |
Java 14 | Mars 2020 |
Java 15 | Septembre 2020 |
Java 16 | Mars 2021 |
Java 17 (LTS) | Septembre 2021 |
Je vous propose de découvrir dans cet article les nouveautés apportées au langage dans les JDK 15 et 16, et aussi de voir ce que pourrait apporter les futures versions de Java dans les années à venir.
Les fonctionnalités apportées par les JDK 15 et 16
Les fonctionnalités finalisées
Blocs de texte
Les Text Blocks, qui comme leur nom l’indique permettent d’écrire un bloc de chaînes de caractères sur plusieurs lignes sans avoir à les concaténer avec des retours chariots et des caractères d’échappement sont enfin disponibles en version définitive dans Java 15.
On peut désormais écrire :
[code lang= »java »]
String query = « » »
SELECT « EMP_ID », « LAST_NAME » FROM « EMPLOYEE_TB »
WHERE « CITY » = ‘INDIANAPOLIS’
ORDER BY « EMP_ID », « LAST_NAME »;
« » »;
[/code]
Au lieu de :
[code lang= »java »]
String query = « SELECT « EMP_ID », « LAST_NAME » FROM « EMPLOYEE_TB »n » +
« WHERE « CITY » = ‘INDIANAPOLIS’n » +
« ORDER BY « EMP_ID », « LAST_NAME »;n »;
[/code]
Records
Présentés avec le JDK 14, les Records sont des POJO moins verbeux (de quoi dire adieu à Lombok ?)
Par exemple :
[code lang= »java »]
record Point(int x, int y) { }
[/code]
Est l’équivalent de :
[code lang= »java »]
class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
int x() { return x; }
int y() { return y; }
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
Point other = (Point) o;
return other.x == x && other.y = y;
}
public int hashCode() {
return Objects.hash(x, y);
}
public String toString() {
return String.format(« Point[x=%d, y=%d] », x, y);
}
}
[/code]
Notez qu’un record
peut également être défini au sein même d’une méthode, de manière locale :
[code lang= »java »]
List<Merchant> findTopMerchants(List<Merchant> merchants, int month) {
// Local record
record MerchantSales(Merchant merchant, double sales) {}
return merchants.stream()
.map(merchant -> new MerchantSales(merchant, computeSales(merchant, month)))
.sorted((m1, m2) -> Double.compare(m2.sales(), m1.sales()))
.map(MerchantSales::merchant)
.collect(toList());
}
[/code]
Filtrage par motif avec instanceof
Afin d’éviter le transtypage après l’utilisation de l’opérateur instanceof
:
[code lang= »java »]
if (obj instanceof String) {
String s = (String) obj; // grr…
…
}
[/code]
On peut maintenant faire :
[code lang= »java »]
if (obj instanceof String s) {
// Let pattern matching do the work!
…
}
[/code]
Les fonctionnalités en aperçu
Les classes scellées
C’est la grosse nouveauté du JDK 15, les classes scellées ont pour objectif de restreindre l’implémentation ou l’héritage de celles-ci.
La déclaration d’une classe comme scellée se fait de la manière suivante :
[code lang= »java »]
package com.example.geometry;
public abstract sealed class Shape
permits Circle, Rectangle, Square {…}
[/code]
Le mot clef permits
sert à lister les classes autorisées à hériter de la classe scellée.
Il est possible d’inclure ces sous-classes directement dans le même fichier où la classe scellée est déclarée, sans avoir à définir permits
:
[code lang= »java »]
package com.example.geometry;
abstract sealed class Shape {…}
… class Circle extends Shape {…}
… class Rectangle extends Shape {…}
… class Square extends Shape {…}
[/code]
Les contraintes apportées aux classes scellées sont les suivantes :
- L’héritage doit être explicité dans la classe parente
- Les classes scellées et leur sous classes doivent être dans le même module ou dans le même package
- Les sous-classes doivent directement hériter de leur classe scellée (sans sous-classe intermédiaire)
- Les sous-classes doivent être déclarées comme
sealed
,non-sealed
oufinal
pour décrire comment la hiérarchie de celles-ci s’achève.
Notez qu’uneinterface
et qu’unrecord
peuvent être définis commesealed
.
Calcul vectoriel
Une nouvelle API permettant de faire du calcul vectoriel est apparue dans le JDK 16. C’est le premier module à rejoindre l’incubateur introduit avec Java 11.
Ce calcul scalaire :
[code lang= »java »]
void scalarComputation(float[] a, float[] b, float[] c) {
for (int i = 0; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}
[/code]
Pourra s’écrire de manière plus explicite ainsi :
[code lang= »java »]
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;
void vectorComputation(float[] a, float[] b, float[] c) {
for (int i = 0; i < a.length; i += SPECIES.length()) {
var m = SPECIES.indexInRange(i, a.length);
// FloatVector va, vb, vc;
var va = FloatVector.fromArray(SPECIES, a, i, m);
var vb = FloatVector.fromArray(SPECIES, b, i, m);
var vc = va.mul(va).
add(vb.mul(vb)).
neg();
vc.intoArray(c, i, m);
}
}
[/code]
Et devrait s’exécuter bien plus rapidement sur les architectures compatibles (x64 et AArch64) tout en fonctionnant de manière dégradée sur les architectures incompatibles avec SIMD (Single instruction multiple data).
Les probables fonctionnalités de Java 17
Filtrage par motif
On le sait déjà, à la suite de la refonte du switch
dans le JDK 12, l’amélioration apportée à l’opérateur instanceof
dans le JDK 14, puis les classes scellées du JDK 15, il y a de très fortes chances que le filtrage par motif soit intégré dans le JDK 17.
On pourrait donc très prochainement écrire ce code en Java :
[code lang= »java »]
int getCenter(Shape shape) {
return switch (shape) {
case Circle c -> … c.center() …
case Rectangle r -> … r.length() …
case Square s -> … s.side() …
};
}
[/code]
Ou encore :
[code lang= »java »]
String formatted =
switch (obj) {
case Integer i -> String.format(« int %d », i);
case Byte b -> String.format(« byte %d », b);
case Long l -> String.format(« long %d », l);
case Double d -> String.format(« double %f », d);
case String s -> String.format(« String %s, s);
default -> String.format(« Object %s », obj);
};
[/code]
Fonctions locales
Déjà présentes dans C# ou Kotlin, les fonctions locales sont des méthodes imbriquées dans un membre, avec une visibilité réservée à leur conteneur.
Il est fort probable que ce genre de code soit possible avec Java 17 :
[code lang= »java »]
public static void main(String[] args) {
int add(int a, int b) {
return a + b;
}
var r1 = add(20, 40);
var r2 = add(40, 60);
}
[/code]
Et ensuite ?
Il s’agit ici de spéculations sur ce que Java pourrait apporter comme fonctionnalités dans les années à venir, très largement inspiré de ce qui se fait déjà dans d’autres langages de programmation, notamment Kotlin.
Déclarations déstructurées
Ce concept permet de décomposer un objet en plusieurs variables, ce qui peut être pratique :
[code lang= »java »]
var person = new Person(“John”, “Doe”);
var (firstname, lastname) = person;
System.out.println(firstname); // Print “John”
[/code]
Coroutines
Les coroutines sont un moyen simple de faire de la programmation non bloquante et asynchrone.
Cela s’écrit de la manière suivant en Kotlin :
[code]
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // launch a new coroutine in background and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println(« World! ») // print after delay
}
println(« Hello, ») // main thread continues while coroutine is delayed
Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}
[/code]
C’est à mon avis une des fonctionnalités qui manque cruellement à Java, où il peut être complexe de faire des traitements asynchrone nativement.
Classes inline
Toujours dans Kotlin, les classes dites inline
sont des classes encapsulant un seul et unique type, afin d’y ajouter de la logique :
[code]
inline class Name(val s: String) {
val length: Int
get() = s.length
fun greet() {
println(« Hello, $s »)
}
}
[/code]
Les avantages à utiliser ce genre de classe sont :
- Pour les développeurs : encapsuler un type dans une classe avec un nom explicite et des fonctions, évitant ainsi l’obsession des primitifs
- Pour la consommation mémoire : aucune instanciation n’est faite lors de l’utilisation de classe inline car le compilateur la remplace par le type encapsulé
La fin des null
Peut-on rêver ?