Pourquoi avons-nous besoin de pause après les déclarations de cas?


94

Pourquoi le compilateur ne met-il pas automatiquement des instructions break après chaque bloc de code dans le commutateur? Est-ce pour des raisons historiques? Quand voudriez-vous que plusieurs blocs de code s'exécutent?


2
J'ai fait une réponse à propos du JDK-12 et des étiquettes de commutation réformées pour ne pas obliger break.
Naman

Réponses:


94

Parfois, il est utile d'avoir plusieurs cas associés au même bloc de code, tels que

case 'A':
case 'B':
case 'C':
    doSomething();
    break;

case 'D':
case 'E':
    doSomethingElse();
    break;

etc. Juste un exemple.

D'après mon expérience, il est généralement mauvais de «passer à travers» et d'exécuter plusieurs blocs de code pour un cas, mais il peut y avoir des utilisations pour cela dans certaines situations.


28
Ajoutez toujours un commentaire le long des lignes // Intentional fallthrough.lorsque vous omettez une pause. Ce n'est pas tant un mauvais style que "facile d'oublier une pause accidentellement" à mon avis. PS Bien sûr pas dans des cas simples comme dans la réponse elle-même.
double du

@doublep - Je suis d'accord. À mon avis, je l'éviterais si possible, mais si cela a du sens, assurez-vous que ce que vous faites est très clair.
WildCrustacean

6
@doublep: Je ne me soucierais pas du commentaire si les multiples cases sont empilés de cette façon. S'il y a du code entre eux, oui, le commentaire est probablement mérité.
Billy ONeal

4
J'imagine un langage où vous pouvez déclarer plusieurs cas en un seul case, comme ceci case 'A','B','C': doSomething(); case 'D','E': doSomethingElse();:, sans avoir besoin d'une pause entre les cas. Pascal pourrait faire cela: "L'instruction case compare la valeur de l'expression ordinale à chaque sélecteur, qui peut être une constante, un sous-intervalle ou une liste d'entre eux séparés par des virgules." ( wiki.freepascal.org/Case )
Christian Semrau

32

Historiquement , c'est parce que le casedéfinissait essentiellement un label, également connu comme le point cible d'un gotoappel. L'instruction switch et ses cas associés ne représentent en réalité qu'une branche à plusieurs voies avec plusieurs points d'entrée potentiels dans un flux de code.

Cela dit, il a été noté un nombre presque infini de fois que breakc'est presque toujours le comportement par défaut que vous préférez avoir à la fin de chaque cas.


30

Java vient de C et c'est la syntaxe de C.

Il y a des moments où vous souhaitez que plusieurs instructions case n'aient qu'un seul chemin d'exécution. Vous trouverez ci-dessous un exemple qui vous indiquera le nombre de jours par mois.

class SwitchDemo2 {
    public static void main(String[] args) {

        int month = 2;
        int year = 2000;
        int numDays = 0;

        switch (month) {
            case 1:
            case 3:
            case 5:
            case 7:
            case 8:
            case 10:
            case 12:
                numDays = 31;
                break;
            case 4:
            case 6:
            case 9:
            case 11:
                numDays = 30;
                break;
            case 2:
                if ( ((year % 4 == 0) && !(year % 100 == 0))
                     || (year % 400 == 0) )
                    numDays = 29;
                else
                    numDays = 28;
                break;
            default:
                System.out.println("Invalid month.");
                break;
        }
        System.out.println("Number of Days = " + numDays);
    }
}

4
Quelqu'un a visé la flèche vers le haut et l'a raté? Ou peut-être qu'ils avaient un boeuf avec votre style d'accolade ou en retrait ...
Jim Lewis

Je ne sais pas, alors ayez un +1 de ma part. Ceci est un exemple où le fall-through aide, même si j'aurais vraiment aimé que Java ait choisi une déclaration de cas plus moderne. EVALUATE-WHEN-OTHERWISE de Cobol est bien plus puissant et est antérieur à Java. L'expression de correspondance de Scala est un exemple moderne de ce qui pourrait être fait.
Jim Ferrans

1
Mes étudiants seraient publiquement fouettés pour avoir fait cela. C'est moche de coyote.
ncmathsadist

2
@ncmathsadist Il illustre un point sur une façon de faire quelque chose. Je ne conteste pas que cet exemple est probablement extrême. Mais c'est un exemple concret, qui, je crois, aide les gens à comprendre un concept.
Romain Hippeau le

15

Je pense que c'est une erreur. En tant que construction de langage, il est tout aussi facile à avoir breakque la valeur par défaut et à la place d'avoir un fallthroughmot - clé. La plupart du code que j'ai écrit et lu a une pause après chaque cas.


4
Je suggérerais plutôt continue <case name>ce qui permet de spécifier explicitement avec quelle déclaration de cas continuer;
Vilx-

4
@Vilx Lorsque vous autorisez un arbitraire casedans le courant switch, cela devient simplement un goto. ;-)
Christian Semrau

13

Vous pouvez faire toutes sortes de choses intéressantes avec la chute des cas.

Par exemple, disons que vous voulez faire une action particulière pour tous les cas, mais dans un certain cas, vous voulez faire cette action plus autre chose. L'utilisation d'une instruction switch avec fall-through rendrait les choses assez faciles.

switch (someValue)
{
    case extendedActionValue:
        // do extended action here, falls through to normal action
    case normalActionValue:
    case otherNormalActionValue:
        // do normal action here
        break;
}

Bien sûr, il est facile d'oublier l' breakinstruction à la fin d'un cas et de provoquer un comportement inattendu. Les bons compilateurs vous avertiront lorsque vous omettez l'instruction break.


Le switch / case peut-il être utilisé sur des chaînes en Java?
Steve Kuo

@Steve: Oups, je suppose que pas pour le moment. Selon stackoverflow.com/questions/338206/… , les chaînes seront autorisées dans une future version de Java. (Je fais actuellement la plupart de ma programmation en C #, ce qui autorise les chaînes dans les instructions switch.) J'ai modifié la réponse pour supprimer les guillemets trompeurs.
Zach Johnson

2
@ZachJohnson, bien plus tard, Java 7 permet d'activer les chaînes.
Bob Cross

7

Pourquoi le compilateur ne met-il pas automatiquement des instructions break après chaque bloc de code dans le commutateur?

Laissant de côté la bonne volonté de pouvoir utiliser le même bloc pour plusieurs cas (qui pourraient être à cas particulier) ...

Est-ce pour des raisons historiques? Quand voudriez-vous que plusieurs blocs de code s'exécutent?

C'est principalement pour la compatibilité avec C, et c'est sans doute un ancien hack de l'époque où les gotomots - clés parcouraient la terre. Il ne permettra des choses étonnantes, bien sûr, comme le dispositif de Duff , mais que ce soit un point en sa faveur ou contre est ... argumentative au mieux.


5

Le breakcommutateur after caseest utilisé pour éviter la chute dans les instructions switch. Bien que cela soit intéressant, cela peut maintenant être réalisé grâce aux nouvelles étiquettes de commutation mises en œuvre via JEP-325 .

Avec ces changements, le breakavec chaque commutateur casepeut être évité comme démontré plus loin: -

public class SwitchExpressionsNoFallThrough {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int value = scanner.nextInt();
        /*
         * Before JEP-325
         */
        switch (value) {
            case 1:
                System.out.println("one");
            case 2:
                System.out.println("two");
            default:
                System.out.println("many");
        }

        /*
         * After JEP-325
         */
        switch (value) {
            case 1 ->System.out.println("one");
            case 2 ->System.out.println("two");
            default ->System.out.println("many");
        }
    }
}

Lors de l' exécution du code ci-dessus avec JDK-12 , la sortie comparative pourrait être considérée comme

//input
1
// output from the implementation before JEP-325
one
two
many
// output from the implementation after JEP-325
one

et

//input
2
// output from the implementation before JEP-325
two
many
// output from the implementation after JEP-325
two

et bien sûr la chose inchangée

// input
3
many // default case match
many // branches to 'default' as well

4

Vous n'avez donc pas à répéter le code si vous avez besoin de plusieurs cas pour faire la même chose:

case THIS:
case THAT:
{
    code;
    break;
}

Ou vous pouvez faire des choses comme:

case THIS:
{
   do this;
}
case THAT:
{
   do that;
}

En cascade.

Vraiment sujet aux bogues / confusion, si vous me demandez.


est-ce que cela fonctionne à la fois do thiset do thatpour ceci mais juste do thatpour cela?
JonnyRaa

1
lisez simplement la documentation. C'est terrible! Quelle façon simple d'écrire des bogues!
JonnyRaa

4

En ce qui concerne le dossier historique, Tony Hoare a inventé l'énoncé de cas dans les années 1960, pendant la révolution de la «programmation structurée». La déclaration de cas de Tony prenait en charge plusieurs étiquettes par cas et une sortie automatique sans breakdéclarations puantes . L'exigence d'un explicite breakétait quelque chose qui sortait de la ligne BCPL / B / C. Dennis Ritchie écrit (dans ACM HOPL-II):

Par exemple, la terminaison qui s'échappe d'une instruction switchon BCPL n'était pas présente dans le langage lorsque nous l'avons appris dans les années 1960, et donc la surcharge du mot-clé break pour échapper à l'instruction switch B et C est due à une évolution divergente plutôt qu'à consciente changement.

Je n'ai pas pu trouver d'écrits historiques sur BCPL, mais le commentaire de Ritchie suggère que breakc'était plus ou moins un accident historique. BCPL a résolu le problème plus tard, mais peut-être que Ritchie et Thompson étaient trop occupés à inventer Unix pour être dérangés par un tel détail :-)


Cela devrait obtenir plus de votes. Apparemment, l'OP savait déjà que l'omission breakautorise "l'exécution de plusieurs blocs de code", et se préoccupe davantage de la motivation de ce choix de conception. D'autres ont mentionné l'héritage bien connu de C à Java, et cette réponse a poussé la recherche encore plus loin aux jours pré-C. J'aurais aimé avoir cette correspondance de motifs (bien que très primitive) dès le début.
wlnirvana

3

Java est dérivé de C, dont l'héritage comprend une technique connue sous le nom de Duff's Device . C'est une optimisation qui repose sur le fait que le contrôle passe d'un cas à l'autre, en l'absence de break;déclaration. Au moment où C a été normalisé, il y avait beaucoup de code comme celui-là "dans la nature", et il aurait été contre-productif de changer le langage pour casser de telles constructions.


1

Comme les gens l'ont déjà dit, il s'agit de permettre des retombées et ce n'est pas une erreur, c'est une fonctionnalité. Si trop d' breakinstructions vous ennuient, vous pouvez facilement vous en débarrasser en utilisant des returninstructions à la place. C'est en fait une bonne pratique, car vos méthodes doivent être aussi petites que possible (pour des raisons de lisibilité et de maintenabilité), donc une switchinstruction est déjà assez grande pour une méthode, par conséquent, une bonne méthode ne doit contenir rien d'autre, c'est un exemple:

public class SwitchTester{
    private static final Log log = LogFactory.getLog(SwitchTester.class);
    public static void main(String[] args){
        log.info(monthsOfTheSeason(Season.WINTER));
        log.info(monthsOfTheSeason(Season.SPRING));
        log.info(monthsOfTheSeason(Season.SUMMER));
        log.info(monthsOfTheSeason(Season.AUTUMN));
    }

    enum Season{WINTER, SPRING, SUMMER, AUTUMN};

    static String monthsOfTheSeason(Season season){
        switch(season){
            case WINTER:
                return "Dec, Jan, Feb";
            case SPRING:
                return "Mar, Apr, May";
            case SUMMER:
                return "Jun, Jul, Aug";
            case AUTUMN:
                return "Sep, Oct, Nov";
            default:
                //actually a NullPointerException will be thrown before reaching this
                throw new IllegalArgumentException("Season must not be null");
        }        
    }
}   

L'exécution imprime:

12:37:25.760 [main] INFO lang.SwitchTester - Dec, Jan, Feb
12:37:25.762 [main] INFO lang.SwitchTester - Mar, Apr, May
12:37:25.762 [main] INFO lang.SwitchTester - Jun, Jul, Aug
12:37:25.762 [main] INFO lang.SwitchTester - Sep, Oct, Nov

comme prévu.


0

Le fait de ne pas avoir de pause automatique ajoutée par le compilateur permet d'utiliser un commutateur / cas pour tester des conditions comme 1 <= a <= 3en supprimant l'instruction break de 1 et 2.

switch(a) {
  case 1: //I'm between 1 and 3
  case 2: //I'm between 1 and 3
  case 3: //I'm between 1 and 3
          break;
}

Yecch. Je déteste totalement ça.
ncmathsadist

0

car il y a des situations où vous souhaitez parcourir le premier bloc par exemple pour éviter d'écrire le même code dans plusieurs blocs mais être toujours capable de les diviser pour le contrôle mroe. Il y a aussi une tonne d'autres raisons.


0

C'est une vieille question, mais en fait, je suis tombé sur l'utilisation du cas sans déclaration de rupture aujourd'hui. Ne pas utiliser break est en fait très utile lorsque vous devez combiner différentes fonctions en séquence.

par exemple en utilisant des codes de réponse http pour authentifier l'utilisateur avec un jeton de temps

code de réponse du serveur 401 - le jeton est obsolète -> régénérer le jeton et connecter l'utilisateur.
code de réponse du serveur 200 - le jeton est OK -> connecter l'utilisateur.

dans les déclarations de cas:

case 404:
case 500:
        {
            Log.v("Server responses","Unable to respond due to server error");
            break;
        }
        case 401:
        {
             //regenerate token
        }
        case 200:
        {
            // log in user
            break;
        }

En utilisant cela, vous n'avez pas besoin d'appeler la fonction utilisateur de connexion pour la réponse 401 car lorsque le jeton est régénéré, le runtime saute dans le cas 200.


0

Vous pouvez facilement séparer d'autres types de nombre, de mois, de décompte.
C'est mieux alors si dans ce cas;

public static void spanishNumbers(String span){

    span = span.toLowerCase().replace(" ", "");
    switch (span){
     case "1":    
     case "jan":  System.out.println("uno"); break;    
     case "2":      
     case "feb":  System.out.println("dos"); break;    
     case "3":     
     case "mar":  System.out.println("tres"); break;   
     case "4":   
     case "apr":  System.out.println("cuatro"); break;
     case "5":    
     case "may":  System.out.println("cinco"); break;
     case "6":     
     case "jun":  System.out.println("seis"); break;
     case "7":    
     case "jul":  System.out.println("seite"); break;
     case "8":    
     case "aug":  System.out.println("ocho"); break;
     case "9":   
     case "sep":  System.out.println("nueve"); break;
     case "10":    
     case "oct": System.out.println("diez"); break;
     }
 }

0

Je travaille maintenant sur un projet dont j'ai besoin breakdans mon instruction switch, sinon le code ne fonctionnera pas. Soyez avec moi et je vais vous donner un bon exemple de pourquoi vous breaken avez besoin dans votre déclaration de changement.

Imaginez que vous ayez trois états, l'un qui attend que l'utilisateur entre un nombre, le second pour le calculer et le troisième pour imprimer la somme.

Dans ce cas, vous avez:

  1. State1 - Attendez que l'utilisateur entre un nombre
  2. State2 - Imprimer la somme
  3. state3 - Calculer la somme

En regardant les états, vous voudriez que l'ordre d'exaction commence sur state1 , puis state3 et enfin state2 . Sinon, nous n'imprimerons que les entrées des utilisateurs sans calculer la somme. Juste pour clarifier à nouveau, nous attendons que l'utilisateur entre une valeur, puis calculons la somme et imprime la somme.

Voici un exemple de code:

while(1){
    switch(state){
      case state1:
        // Wait for user input code
        state = state3; // Jump to state3
        break;
      case state2:
        //Print the sum code
        state = state3; // Jump to state3;
      case state3:
        // Calculate the sum code
        state = wait; // Jump to state1
        break;
    }
}

Si nous ne l'utilisons pas break, il s'exécutera dans cet ordre, état1 , état2 et état3 . Mais en utilisant break, nous évitons ce scénario, et pouvons commander dans la bonne procédure qui est de commencer par state1, puis state3 et last but not least state2.


-1

Exactement, car avec un placement intelligent, vous pouvez exécuter des blocs en cascade.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.