JEP 409 Classes scellées

Contexte

Je vous avais présenté la fonctionnalité au moment de la sortie du JDK 16. Cette fonctionnalité était en mode Aperçu. Maintenant que c’est en standard dans le langage, profitons pour regarder cela de plus prêt.

avertissementAucune modification a été apporté par rapport à la JEP 397 Sealed classes (Second Preview)

Principe

Le but est d’enrichir l’héritage afin d’amener plus de contrôle. En effet, actuellement, nous avons uniquement le mot clé final. Cela permet de bloquer l’héritage. Donc, nous ne peuvons pas avoir des classes dérivées.

package fr.exemple.;

public final class Forme {
	...
}

public final class Rectangle extends Forme { // Erreur de compilation
 	...
}

Les classes scellées sont un moyen pour le développeur d’avoir plus de contrôle. Pour cela, nous avons deux nouveaux mot clés sealed et non-sealed :

  • sealed : permet d’avoir des classes dérivées à condition qu’elles fassent partir des classes autorisées pour cela il y a le mot clé permits à utiliser.

  • non-sealed : permet de ne pas limiter les classes dérivées.

Par exemple, nous avons :

package fr.exemple.geometrie;

public abstract sealed class Forme
    permits Cercle, Rectangle {
	...
}

Nous définissons une classe abstraite Forme dont seules les classes Cercle et Rectangle sont autorisées comme classes dérivées.

Par conséquent, si le développeur essaie d’écrire l’héritage pour une autre classe. Cela provoquera une erreur de compilation si nous essayons.

package fr.exemple.geometrie;

public class Triangle extends Forme  { // Erreur de compilation
	...
}

Précision pour le mot-clé permits

Si les classes dérivées sont dans le même fichier. Le mot clé permits n’est pas nécessaire.

package fr.exemple.geometrie;

public abstract sealed class Forme {
	...

    public final class Cercle extends Forme {
        ...
    }

    public non-sealed class Rectangle extends Forme {
        ...
    }

}

Précision pour le mot-clé non-sealed

Du fait que nous avons précisé que la classe Rectangle n’est pas scellée. Nous pouvons hériter de cette classe. Par exemple, nous pouvons avoir la classe Carré définie ainsi :

package fr.exemple.geometrie;

public final class Carre extends Rectangle {
	...
}

Interfaces

Cela fonctionne aussi avec les interfaces

package fr.exemple.geometrie;

public sealed interface Forme
    permits Cercle, Rectangle, Carre {
	...
}

Enregistrements

Les enregistrements peuvent être utilisés pour l’héritage.

avertissementPour rappel, les enregistrements sont des classes. Elles ne peuvent pas hérités d’autres classes (puisque héritage vers Record) mais elles peuvent implémenter une ou plusieurs interfaces.

Ci dessous un exemple avec les enregistrements :

public sealed interface Noeud
	permits Noeud.Feuille, Noeud.Multiplication, Noeud.Addition {

	public double eval();

	public record Feuille(double val) implements Noeud {
		public double eval() {
			return val;
		}
	}
    ...

}

J’avais utilisé cet exemple lors de conférence sur les nouveautés de Java 16. Le code complet de cet exemple est disponible sur mon github.

Exemple d’utilisation

Cela sera peut-être plus utile pour les personnes qui développent des librairies afin de maitriser les classes et éviter que certains classes soient étendues.

Le JDK prévoit son utilisation par exemple pour protéger certaines interfaces. Par exemple, l’interface ClassDesc où la javadoc indique clairement son usage quand les classes scellées seront disponibles en standard.

ClassDesc javadoc