Publié par

Il y a 2 semaines -

Temps de lecture 5 minutes

Introduction au Sealed Classes de Java 15

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 et interface) 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 clause record 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

Publié par

Commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.