Introduction
Le but de cet article est de présenter les sealed classes (que je nommerais dans la suite de l’article classes scellées) prévu dans Java 15.
Pour information, cette fonctionnalité est disponible en avant-première, ce qui nécessite d’utiliser l’option --enable-preview
, et des évolutions majeures peuvent survenir dans les prochaines versions de Java. De plus, cette fonctionnalité a été testée avec la pré-version OpenJDK Runtime Environment (build 15+36-1562)
, Java 15 devant sortir autour du 15 septembre 2020.
Qu’est-ce qu’une sealed class
?
Les sealed
classes et sealed
interfaces sont des classes et interfaces qui limitent les classes qui peuvent les étendre/implémenter. Les principaux buts de cette évolution sont d’ajouter un contrôle sur les implémentations des classes et d’avoir des restrictions sur l’utilisation d’une super classe.
Pour mettre en place une classe scellée, il faut :
- Ajouter la clause
sealed
à sa classe parente. Il est possible d’ajouter cette clause sur toutes formes de classes extensibles (class
,abstract class
etinterface
) et elles conservent leurs propriétés propres. - Les implémentations de la classe scellée peuvent être :
- Soit au sein de la même unité de compilation.
- Soit à l’extérieur de l’unité de compilation. Dans ce cas, la liste de toutes les implémentations doit être déclarée via la clause
permit
dans la déclaration de la classe scellées. Ses implémentations extérieures doivent se situer :- Dans le même module Java que la classe scellée.
- Ou dans le même package Java dans le cas d’utilisation d’un module non nommé.
- Les classes descendantes doivent ajouter l’une des clauses suivantes :
final
(Cela fonctionne avec la clauserecord
qui part définition est une implémentation de classe finale).
sealed
non-sealed
: dans ce cas la classe fonctionne comme une classe classique et peut avoir autant de classes descendantes que nous le souhaitons.
Exemples
Voici une série d’exemples d’implémentation de classes scellées.
Premier exemple : Expressions Algébriques
Commençons par un exemple de hiérarchie de classes tentant de définir des expressions algébriques
Tout d’abord définissons la classe parente définissant une expression :
Fichier Expr.java
:
package xyz.coincoin.amber.sealed.expression; public sealed class Expr permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { }
Puis définissons les classes descendantes étendant la classe parente scellée :
Fichier ConstantExpr.java
:
package xyz.coincoin.amber.sealed.expression; public final class ConstantExpr extends Expr { //... }
Fichier NegExpr.java
:
package xyz.coincoin.amber.sealed.expression; public final class NegExpr extends Expr { //... }
Fichier PlusExpr.java
:
package xyz.coincoin.amber.sealed.expression; public final class PlusExpr extends Expr { //... }
Fichier TimesExpr.java
:
package xyz.coincoin.amber.sealed.expression; public final class TimesExpr extends Expr { //... }
À noter que dans ce cas, il est totalement possible d’avoir une instance de la classe Expr
:
var expr = new Expr();
Second exemple : Arbre phylogénétique
Voici un second exemple qui tente d’implémenter une version simplifiée, réduite et non exhaustive de l’arbre phylogénétique.
Définissons un premier niveau :
Fichier Animal.java
package xyz.coincoin.amber.sealed.phylogenetic; public sealed class Animal permits Mammal, Insect, Bird, Reptile, Fish { //... }
Puis un second niveau :
Fichier Mammal.java
package xyz.coincoin.amber.sealed.phylogenetic; public sealed class Mammal extends Animal permits Dog, Cat { //... }
Fichier Insect.java
package xyz.coincoin.amber.sealed.phylogenetic; public non-sealed class Insect extends Animal { //... }
Puis un troisième niveau :
Fichier Cat.java
package xyz.coincoin.amber.sealed.phylogenetic; public final class Cat extends Mammal { //... }
Fichier Dog.java
package xyz.coincoin.amber.sealed.phylogenetic; public final class Dog extends Mammal { //... }
Fichier Ant.java
package xyz.coincoin.amber.sealed.phylogenetic; public class Ant extends Insect { //... }
Dans cet exemple, j’ai définie une hiérarchie de classe sur plusieurs niveaux. J’ai considéré qu’il y avait un nombre fini de mammifère différents (qui est très réduit pour alléger l’exemple) mais qu’il pouvait exister un nombre illimité d’espèce d’insectes différents.
Exemple avec une interface et des records
Voici un exemple plus concis qui implémente une hiérarchie de classe définissant des formes :
Fichier Shape.java
:
package xyz.coincoin.amber.sealed.record; public sealed interface Shape { record Circle() implements Shape {} record Square() implements Shape {} sealed interface Rectangle extends Shape {} record FilledRectangle() implements Rectangle {} }
Comme on peut le voir, j’ai omis la clause permit
car toutes les implémentations de mon interface Shape
se trouvent dans la même unité de compilation que cette dernière.
Autres implémentations dans d’autre langages
Kotlin
En Kotlin, une classe scellée (sealed class
) est une classe abstraite, il n’est pas possible de sceller une interface en Kotlin. Ses descendances doivent forcement se situer dans la même unité de compilation que la classe scellée. Voici un exemple simple :
sealed class Expr data class Const(val number: Double) : Expr() data class Sum(val e1: Expr, val e2: Expr) : Expr() object NotANumber : Expr()
Scala
En Scala comme en Java, il est possible de définir scellée (avec la clause sealed
) toute forme de classe extensible (trait
, abstract class
, class
). Comme en Kotlin, par contre, toutes les classes descendantes d’une classe scellée doivent forcement être déclarées dans la même unité de compilation. Et comme pour le précédent paragraphe, voici un exemple simple :
sealed abstract class Furniture case class Couch() extends Furniture case class Chair() extends Furniture
Conclusion
Nous avons vu dans cette brève introduction comment il sera bientôt possible d’avoir des classes scellées de manière simple dans Java. Cette fonctionnalité (qui existe déjà dans des langages tel que Kotlin ou Scala) ajoutera des contrôles sur la hiérarchie des classes. Cette fonctionnalité permettra, couplée avec les record introduits en Java 14, d’implémenter des types algébriques en Java de manière simple et concise. De plus, les classes scellées permettront d’avoir le support de l’analyse exhaustive grâce au pattern matching sur switch
(Pour ce dernier point je vous renvoie à l’article de Brian Goetz : Pattern Matching for Java).
References
- JEP 360: Sealed Classes (Preview)
- Sealed Classes – Kotlin Programming Language
- Le paragraphe Sealed classes de la page Pattern Matching | Tour of Scala | Scala Documentation