Switch moderne : flèches, valeurs et enums

Publié le · Mis à jour le

L'idée en une phrase

Depuis Java 14, le switch s'écrit avec des flèches (->) : plus de break à oublier, il peut renvoyer une valeur, et sur un enum, le compilateur vérifie que tu n'as oublié aucun cas. C'est la forme à utiliser partout.

Une image pour comprendre : l'aiguillage

Un switch, c'est un aiguillage de train : selon la valeur, le train part sur une voie ou sur une autre.

  • L'ancien switch a un défaut : s'il n'y a pas de butoir (le break) au bout d'une voie, le train continue sur la voie suivante.
  • Le nouveau switch avec -> : chaque voie mène à sa propre gare, et le train s'arrête toujours au bon endroit.

Et sur un enum, le compilateur vérifie en plus que chaque valeur possible a sa voie. Aucun train ne peut partir dans le décor.

Le problème de l'ancien switch

String libelle;
switch (statut) {
    case EN_COURS:
        libelle = "En cours de préparation";
    case PAYEE:                          // ❌ break oublié : EN_COURS continue ici
        libelle = "Payée";
        break;
    case ANNULEE:
        libelle = "Annulée";
        break;
}

Deux bugs dans ce code, et le compilateur ne dit rien :

  1. Le break oublié : une commande en cours s'affiche « Payée ».
  2. Si quelqu'un ajoute EXPEDIEE à l'enum, aucun cas ne la traite et libelle n'a pas de valeur.

La forme moderne : switch avec flèches

String libelle = switch (statut) {
    case EN_COURS -> "En cours de préparation";
    case PAYEE    -> "Payée";
    case ANNULEE  -> "Annulée";
}; // ✅ le point-virgule final est obligatoire : c'est une affectation

Ce qui change :

  • pas de break : chaque flèche ne fait que son cas, impossible de « tomber » dans le suivant ;
  • le switch renvoie une valeur : on l'affecte directement à libelle ;
  • le compilateur vérifie que tous les cas sont traités.

Le compilateur vérifie l'enum pour toi

C'est le vrai gain. Imagine qu'un collègue ajoute une valeur à l'enum :

enum Statut { EN_COURS, PAYEE, ANNULEE, EXPEDIEE } // nouvelle valeur

Chaque switch qui renvoie une valeur et oublie EXPEDIEE ne compile plus. Le compilateur te montre exactement tous les endroits à mettre à jour, au lieu d'un bug découvert en production.

Plusieurs valeurs pour un même cas

boolean modifiable = switch (statut) {
    case EN_COURS            -> true;
    case PAYEE, ANNULEE, EXPEDIEE -> false; // ✅ plusieurs valeurs séparées par des virgules
};

Quand il n'y a pas de valeur à renvoyer

La flèche s'utilise aussi pour exécuter une action. C'est la forme à préférer, même sans valeur :

switch (statut) {
    case PAYEE    -> notificationService.confirmerPaiement(commande);
    case ANNULEE  -> notificationService.confirmerAnnulation(commande);
    case EN_COURS, EXPEDIEE -> { } // rien à faire, mais c'est écrit explicitement
}

Note : dans cette forme (sans valeur renvoyée), le compilateur n'oblige pas à traiter tous les cas. Lister quand même tous les cas, même ceux où il n'y a rien à faire, montre au lecteur que tu y as pensé.

Un cas qui demande plusieurs lignes

Entre accolades, le mot-clé yield donne la valeur du cas :

BigDecimal fraisDePort = switch (statut) {
    case ANNULEE -> BigDecimal.ZERO;
    case EN_COURS, PAYEE, EXPEDIEE -> {
        BigDecimal base = new BigDecimal("4.90");
        yield commande.montant().compareTo(SEUIL_GRATUIT) >= 0 ? BigDecimal.ZERO : base;
    }
};

yield n'est pas un return : il sort du switch, pas de la méthode.

Astuce : dès qu'un bloc dépasse trois ou quatre lignes, extrais-le dans une méthode : case EN_COURS, PAYEE, EXPEDIEE -> calculerFrais(commande);. Le switch reste lisible d'un coup d'œil.

Pas de default sur un enum

C'est contre-intuitif, mais important :

String libelle = switch (statut) {
    case EN_COURS -> "En cours de préparation";
    case PAYEE    -> "Payée";
    default       -> "Autre"; // ❌ EXPEDIEE, ajoutée plus tard, tombera ici sans alerte
};

Le default attrape « tout le reste ». Le compilateur considère donc que tout est traité et ne te prévient plus quand une nouvelle valeur est ajoutée. Tu perds la vérification qui faisait tout l'intérêt de la forme moderne.

Sur un enum, liste toutes les valeurs, sans default.

Le default reste nécessaire quand les valeurs possibles ne sont pas connues à l'avance : une String, un int…

String jour = switch (numeroJour) {
    case 6, 7 -> "week-end";
    case 1, 2, 3, 4, 5 -> "semaine";
    default -> throw new IllegalArgumentException("Jour invalide : " + numeroJour); // ✅
};

Et si la valeur est null ?

Un switch sur une valeur null lance une NullPointerException avant même de regarder les cas. Si le statut peut être absent, vérifie-le avant, ou mieux, fais en sorte qu'il ne puisse pas l'être (un champ obligatoire dans l'entité, une valeur par défaut à la création).

Quand le même switch revient partout

Si tu écris le même switch sur statut dans cinq classes différentes pour obtenir un libellé, l'information a sa place dans l'enum lui-même :

public enum Statut {
    EN_COURS("En cours de préparation"),
    PAYEE("Payée"),
    ANNULEE("Annulée"),
    EXPEDIEE("Expédiée");

    private final String libelle;

    Statut(String libelle) {
        this.libelle = libelle;
    }

    public String libelle() {
        return libelle;
    }
}
String texte = commande.statut().libelle(); // ✅ plus besoin de switch

Ajouter une valeur à l'enum oblige alors à lui donner un libellé, au même endroit. Le switch reste utile quand le traitement dépend du contexte (une notification ici, un calcul de frais là) plutôt que d'une simple information attachée à la valeur.

Pour aller plus loin : le switch sur un type (Java 21)

Tu croiseras peut-être un switch qui teste le type d'un objet plutôt que sa valeur :

String decrire(Paiement paiement) {
    return switch (paiement) {
        case PaiementCarte carte       -> "Carte se terminant par " + carte.quatreDerniersChiffres();
        case PaiementVirement virement -> "Virement de " + virement.banque();
    };
}

Chaque cas teste le type et donne directement une variable du bon type, sans conversion. Associé à une interface sealed (qui liste à l'avance tous ses types possibles), le compilateur vérifie, comme pour un enum, qu'aucun type n'est oublié.

C'est une fonctionnalité récente, plutôt utilisée pour modéliser des cas métier avancés. Retiens surtout que la règle est la même : tous les cas listés, pas de default inutile.

Les bonnes habitudes

  1. Toujours la flèche ->, jamais l'ancienne forme avec : et break.
  2. Utilise le switch comme une valeur quand il sert à calculer quelque chose : String libelle = switch (...).
  3. Pas de default sur un enum : liste toutes les valeurs, le compilateur te préviendra des oublis.
  4. Des cas courts : au-delà de trois ou quatre lignes, extrais une méthode.
  5. Le même switch répété partout : mets l'information dans l'enum.
  6. Un default qui ne devrait jamais arriver (sur une String, un int) lance une exception plutôt que de renvoyer une valeur au hasard.

À retenir

  • La forme -> supprime le break et le risque de « tomber » dans le cas suivant.
  • Un switch peut renvoyer une valeur, et sur un enum, le compilateur vérifie que tous les cas sont traités.
  • Un default sur un enum désactive cette vérification : à éviter.
  • case A, B -> regroupe plusieurs valeurs ; yield donne la valeur d'un bloc entre accolades.
  • Une information répétée dans plusieurs switch a sa place dans l'enum.

Avis sur l'article

Pas encore de note : soyez le premier à donner votre avis.

Les avis sont publiés après validation par le modérateur.

Votre note