Blog Arolla

Java next-gen

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 :

String query = """
               SELECT "EMP_ID", "LAST_NAME" FROM "EMPLOYEE_TB"
               WHERE "CITY" = 'INDIANAPOLIS'
               ORDER BY "EMP_ID", "LAST_NAME";
               """;

Au lieu de :

String query = "SELECT \"EMP_ID\", \"LAST_NAME\" FROM \"EMPLOYEE_TB\"\n" +
               "WHERE \"CITY\" = 'INDIANAPOLIS'\n" +
               "ORDER BY \"EMP_ID\", \"LAST_NAME\";\n";

Records

Présentés avec le JDK 14, les Records sont des POJO moins verbeux (de quoi dire adieu à Lombok ?)
Par exemple :

record Point(int x, int y) { }

Est l'équivalent de :

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);
    }
}

Notez qu'un record peut également être défini au sein même d'une méthode, de manière locale :

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());
}

Filtrage par motif avec instanceof

Afin d'éviter le transtypage après l'utilisation de l'opérateur instanceof :

if (obj instanceof String) {
    String s = (String) obj;    // grr...
    ...
}

On peut maintenant faire :

if (obj instanceof String s) {
    // Let pattern matching do the work!
    ...
}

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 :

package com.example.geometry;

public abstract sealed class Shape
    permits Circle, Rectangle, Square {...}

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 :

package com.example.geometry;

abstract sealed class Shape {...}
... class Circle    extends Shape {...}
... class Rectangle extends Shape {...}
... class Square    extends Shape {...}

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 ou final pour décrire comment la hiérarchie de celles-ci s'achève.
    Notez qu'une interface et qu'un record peuvent être définis comme sealed.

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 :

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;
   }
}

Pourra s'écrire de manière plus explicite ainsi :

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);
    }
}

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 :

int getCenter(Shape shape) {
    return switch (shape) {
        case Circle c    -> ... c.center() ...
        case Rectangle r -> ... r.length() ...
        case Square s    -> ... s.side() ...
    };
}

Ou encore :

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);
    };

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 :

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); 
    } 

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 :

var person = new Person(“John”, “Doe”);
var (firstname, lastname) = person;
System.out.println(firstname); // Print “John”

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 :

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
}

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 :

inline class Name(val s: String) {
    val length: Int
        get() = s.length

    fun greet() {
        println("Hello, $s")
    }
} 

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 ?

Plus de publications

Comments are closed.