Java 14 : Switch expression

Ce troisième article détaille les évolutions, proposées en standard, dans l’instruction switch en Java 14.

Cet article fait partie d’une série détaillant les principales fonctionnalités proposées dans Java 14 :

 

Historiquement depuis la première version 1.0 de Java, l’instruction switch est utilisée comme une structure de contrôle permettant d’exécuter des traitements selon la valeur qui lui est fournie.

Sa syntaxe, inspirée par celle de C/C++, n’est pas exempte de défaut :

  • une seule valeur par instruction case
  • fall through par défaut (qui peut être à l’origine de bugs subtils en cas d’oubli d’une instruction break) pour permettre d’exécuter un même traitement pour plusieurs valeurs
  • il n’y a pas de portée spécifique pour les variables définies dans les traitements d’une clause case

Les instructions switch sont souvent répétitives (utilisation d’instructions case et break) et sujettes à erreurs (oubli d’instructions break par exemple).

    switch (experience) {
      case 1 :
        System.out.println("Débutant");
        break;
      case 2 :
        System.out.println("Junior");
        break;
      case 3 :
        System.out.println("Confirmé");
        break;
      case 4 :
        System.out.println("Sénior");
        break;
      case 5 :
        System.out.println("Expert");
        break;
    }

Le but de la JEP 361 est de permettre d’utiliser l’instruction switch aussi comme une expression et de faire évoluer sa syntaxe.

Des évolutions ont été proposées dans le langage dans deux versions en mode preview :

  • La JEP 315 en Java 12 : première preview qui fait évoluer la syntaxe et le rôle de l’instruction switch (nouvelle syntaxe avec l’opérateur arrow, utilisation comme une expression, utilisation de multiples valeurs dans les clauses case, …)
  • La JEP 354 en Java 13 : une seconde preview qui a introduit l’identifiant restreint yield à la place de break pour retourner les valeurs

En Java 14, la JEP 361 intègre les évolutions dans l’instruction switch en tant que fonctionnalité standard.

Les types utilisables dans une instruction switch sont toujours les mêmes int, short, byte, char (et leurs wrappers respectifs), énumération et String.

 

La nouvelle syntaxe

Une nouvelle syntaxe simplifie l’utilisation de l’instruction switch. Elle utilise l’opérateur arrow.

Au-delà du simple remplacement de « : » par « -> », plusieurs simplifications sont introduites.

A la droite de l’opérateur ->, il ne peut y avoir qu’une expression, une instruction unique ou un bloc de code.

Contrairement à la syntaxe historique, si plusieurs instructions doivent être exécutées, il faut les regrouper dans un bloc de code.

Cela est justifié par le fait qu’il n’y a pas de fall-through avec la nouvelle syntaxe.

    switch (experience) {
      case 1 -> System.out.println("Débutant");
      case 2 -> System.out.println("Junior");
      case 3 -> System.out.println("Confirmé");
      case 4 -> System.out.println("Sénior");
      case 5 -> System.out.println("Expert");
    }

Cette nouvelle syntaxe peut être utilisée lorsqu’une instruction switch est utilisée comme structure de contrôle ou comme expression.

 

Les valeurs multiples dans les clauses case

Il est possible de définir plusieurs valeurs dans une clause case en les séparant par une virgule.

Avec la nouvelle syntaxe

    switch (experience) {
      case 1, 2 -> {
        System.out.print("Débutant");
        System.out.println(" et junior");
      }
      case 3, 4 -> System.out.println("Confirmé et sénior");
      case 5    -> System.out.println("Expert");
    }

Mais aussi avec la syntaxe historique

    switch (experience) {
      case 1, 2:
        System.out.print("Débutant");
        System.out.println(" et junior");
        break;
      case 3, 4:
        System.out.println("Confirmé et sénior");
        break;
      case 5:
        System.out.println("Expert");
        break;
    }

 

L’utilisation de l’instruction switch comme une expression

L’instruction switch n’est plus uniquement une structure de contrôle permettant d’exécuter du code selon la valeur de la variable passée en paramètre. Elle peut aussi être utilisé comme une expression et donc retourner une valeur.

Avec la nouvelle syntaxe, c’est très simple, il suffit de fournir la valeur retournée à la droite de l’opérateur arrow.

L’utilisation de l’instruction switch comme une expression implique plusieurs contraintes :

  • Toutes les valeurs du type testé doivent être prises en compte : dans la pratique cela implique de toujours utiliser une clause default sauf pour une énumération dont toutes les valeurs sont prises en compte dans les cases
  • Si le switch est la dernière instruction de la ligne, elle doit se terminer par un « ; »

L’utilisation comme une expression peut se faire avec les deux syntaxes.

Avec la nouvelle syntaxe

    String libelle = switch (experience) {
      case 1, 2 -> "Débutant et junior";
      case 3, 4 -> "Confirmé et sénior";
      case 5    -> "Expert";
      default   -> throw new IllegalArgumentException("valeur experience invalide");
    };
    System.out.println(libelle);

Mais aussi avec la syntaxe historique : dans ce cas il faut utiliser l’identifiant restreint yield. yield est, comme var, un identifiant restreint : ils peuvent être utilisés comme identifiant mais ont un rôle dans des cas particuliers. Pour yield, c’est l’utilisation dans les traitements d’un case.

    libelle = switch (experience) {
      case 1, 2 : yield "Débutant et junior";
      case 3, 4 : yield "Confirmé et sénior";
      case 5    : yield "Expert";
      default   : throw new IllegalArgumentException("valeur experience invalide");
    };
    System.out.println(libelle);

Note : dans la première preview en Java 12, l’instruction break était utilisée pour retourner une valeur. Suite aux retours de la JEP 325, la seconde preview en Java 13 remplace l’utilisation de l’instruction break pour retourner une valeur par yield. Cela évite la confusion avec l’utilisation de break dans un switch comme une structure de contrôle.

Cela implique qu’un switch comme expression utilise yield pour retourner une valeur, et qu’un switch comme structure de contrôle ne retournant pas de valeur doit utiliser break dans la syntaxe historique.

yield doit aussi être utilisé dans un bloc de code avec la nouvelle syntaxe pour préciser la valeur à retourner.

    libelle = switch (experience) {
      case 1, 2 -> {
        System.out.println("cas 1 et 2");
        yield "Débutant et junior";
      }
      case 3, 4 -> "Confirmé et sénior";
      case 5    -> "Expert";
      default   -> throw new IllegalArgumentException("valeur experience invalide");
    };
    System.out.println(libelle);

 

Les 4 formes d’utilisation de l’instruction switch

Comme vu précédemment, les deux syntaxes et les deux rôles peuvent se combiner, ce qui donne 4 formes d’utilisation.

Switch comme structure de contrôle avec la syntaxe historique

  • C’est la forme historique bien connue
  • Fall-throught par défaut
  • default facultatif

Switch comme structure de contrôle avec la nouvelle syntaxe

  • Chaque case doit avoir une instruction ou un bloc de code
  • Pas de fall-through
  • default facultatif

Switch comme expression avec la syntaxe historique

  • Fall-throught par défaut
  • Exhaustivité des cas obligatoire
  • yield pour retourner une valeur

Switch comme expression avec la nouvelle syntaxe

  • Chaque case doit retourner une valeur ou lever une exception, yield pour retourner une valeur dans un bloc de code
  • Pas de fall-through
  • Exhaustivité des cas obligatoire

Il conviendra de tenir compte des contraintes de chacune des formes lors de leur mise en œuvre et de définir des bonnes pratiques d’utilisation avec l’expérience de leurs utilisations.

 

Les situations illicites

Plusieurs situations seront vérifiées par le compilateur et pourront émettre une erreur :

  • Il n’est pas possible de mixer les deux syntaxes dans une même instruction switch
  • Un switch utilisé comme expression est une poly expression : si le type de la variable qui reçoit la valeur du switch est défini, toutes les valeurs retournées doivent être de ce type, sinon il faut utiliser var
  • Un switch comme structure de contrôle ne peut pas retourner de valeur

 

Conclusion

Les évolutions dans la syntaxe et la possibilité d’utiliser un switch comme une expression, maintenant standard, ouvrent de nouvelles perspectives pour les développeurs.

Les quatre formes d’utilisation de l’instruction switch requièrent cependant de tenir compte des contraintes inhérentes à chacune des formes.

Les modifications apportées à l’instruction switch visent aussi à préparer le terrain pour permettre la mise en œuvre du pattern matching dans la JEP 305.

Le prochain article de cette série détaillera une autre évolution dans la syntaxe du langage Java : les blocs de textes.