Warum brauchen wir nach Fallaussagen eine Pause?

93

Warum setzt der Compiler nicht automatisch break-Anweisungen nach jedem Codeblock im Switch? Ist es aus historischen Gründen? Wann sollen mehrere Codeblöcke ausgeführt werden?

unj2
quelle
2
Antwort auf JDK-12 und Switch-Labels wurden reformiert, um kein Mandat zu erteilen break.
Naman

Antworten:

93

Manchmal ist es hilfreich, mehrere Fälle mit demselben Codeblock zu verknüpfen, z

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

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

usw. Nur ein Beispiel.

Nach meiner Erfahrung ist es normalerweise ein schlechter Stil, "durchzufallen" und mehrere Codeblöcke für einen Fall ausführen zu lassen, aber in einigen Situationen kann es Verwendungen dafür geben.

Wildkrustentier
quelle
28
Fügen Sie einfach immer einen Kommentar hinzu, // Intentional fallthrough.wenn Sie eine Pause auslassen. Es ist meiner Meinung nach weniger ein schlechter Stil als "leicht, eine Pause versehentlich zu vergessen". PS Natürlich nicht in einfachen Fällen wie in der Antwort selbst.
Doublep
@doublep - ich stimme zu. Meiner Meinung nach würde ich es nach Möglichkeit vermeiden, aber wenn es Sinn macht, stellen Sie sicher, dass klar ist, was Sie tun.
WildCrustacean
6
@doublep: Ich würde mich nicht um den Kommentar kümmern, wenn die mehreren cases auf diese Weise gestapelt werden. Wenn sich dazwischen Code befindet, ist der Kommentar wahrscheinlich verdient.
Billy ONeal
4
Ich stelle mir eine Sprache , wo man mehrere Fälle innerhalb einer erklären kann case, etwa so: case 'A','B','C': doSomething(); case 'D','E': doSomethingElse();, ohne eine Pause zwischen den Fällen zu benötigen. Pascal könnte dies tun: "Die case-Anweisung vergleicht den Wert des Ordnungsausdrucks mit jedem Selektor, der eine Konstante, ein Unterbereich oder eine durch Kommas getrennte Liste sein kann." ( wiki.freepascal.org/Case )
Christian Semrau
32

Historisch gesehen liegt dies daran, dass im caseWesentlichen ein definiert wurde label, der auch als Zielpunkt eines gotoAnrufs bezeichnet wird. Die switch-Anweisung und die zugehörigen Fälle stellen wirklich nur einen Mehrwegezweig mit mehreren potenziellen Eintrittspunkten in einen Codestrom dar.

Trotzdem wurde fast unendlich oft festgestellt, dass dies breakfast immer das Standardverhalten ist, das Sie am Ende eines jeden Falles lieber hätten.

Bob Cross
quelle
29

Java kommt von C und das ist die Syntax von C.

Es gibt Zeiten, in denen mehrere case-Anweisungen nur einen Ausführungspfad haben sollen. Unten sehen Sie ein Beispiel, das Ihnen sagt, wie viele Tage in einem Monat.

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);
    }
}
Romain Hippeau
quelle
4
Jemand zielte auf den Aufwärtspfeil und verfehlte? Oder vielleicht hatten sie ein Rindfleisch mit Ihrem Zahnspangenstil oder Einkerbung ...
Jim Lewis
Keine Ahnung, also +1 von mir. Dies ist ein Beispiel, bei dem der Fall-Through hilft, obwohl ich mir wirklich wünschte, Java hätte eine modernere Fallaussage ausgewählt. Cobols EVALUATE-WHEN-OTHERWISE ist weitaus leistungsfähiger und älter als Java. Scalas Match-Ausdruck ist ein modernes Beispiel dafür, was getan werden könnte.
Jim Ferrans
1
Meine Schüler würden dafür öffentlich ausgepeitscht. Es ist hässlich Kojote.
ncmathsadist
2
@ncmathsadist Es zeigt einen Punkt auf eine Art, etwas zu tun. Ich bin nicht anderer Meinung, dass dieses Beispiel wahrscheinlich extrem ist. Aber es ist ein Beispiel aus der realen Welt, von dem ich glaube, dass es den Menschen hilft, ein Konzept zu verstehen.
Romain Hippeau
15

Ich denke es ist ein Fehler. Als Sprachkonstrukt ist es genauso einfach breakwie das Standardkonstrukt und hat stattdessen ein fallthroughSchlüsselwort. Der größte Teil des Codes, den ich geschrieben und gelesen habe, hat nach jedem Fall eine Pause.

Präsident James K. Polk
quelle
4
Ich würde eher vorschlagen, continue <case name>welche explizit angeben kann, mit welcher case-Anweisung fortgefahren werden soll.
Vilx
4
@Vilx Wenn ein beliebiger Wert caseinnerhalb des Stroms zugelassen wird switch, wird dies einfach zu einem goto. ;-)
Christian Semrau
13

Sie können alle möglichen interessanten Dinge mit Fallfall tun.

Angenommen, Sie möchten eine bestimmte Aktion für alle Fälle ausführen, aber in einem bestimmten Fall möchten Sie diese Aktion plus etwas anderes ausführen. Die Verwendung einer switch-Anweisung mit Fall-Through würde dies recht einfach machen.

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

Natürlich ist es leicht, die breakAussage am Ende eines Falles zu vergessen und unerwartetes Verhalten zu verursachen. Gute Compiler werden Sie warnen, wenn Sie die break-Anweisung weglassen.

Zach Johnson
quelle
Kann switch / case für Strings in Java verwendet werden?
Steve Kuo
@Steve: Ups, ich denke im Moment nicht. Laut stackoverflow.com/questions/338206/… sind Zeichenfolgen in einer zukünftigen Version von Java zulässig. (Derzeit programmiere ich den größten Teil in C #, wodurch Zeichenfolgen in switch-Anweisungen zulässig sind.) Ich habe die Antwort bearbeitet, um die irreführenden Anführungszeichen zu entfernen.
Zach Johnson
2
@ZachJohnson, viel später erlaubt Java 7 das Einschalten von Strings.
Bob Cross
7

Warum setzt der Compiler nicht automatisch break-Anweisungen nach jedem Codeblock im Switch?

Abgesehen von dem guten Wunsch, den identischen Block für mehrere Fälle verwenden zu können (die speziell behandelt werden könnten) ...

Ist es aus historischen Gründen? Wann sollen mehrere Codeblöcke ausgeführt werden?

Es dient hauptsächlich der Kompatibilität mit C und ist wohl ein uralter Hack aus alten Zeiten, als gotoSchlüsselwörter die Erde durchstreiften. Es tut einige erstaunliche Dinge ermöglichen, natürlich, wie Duff Gerät , aber ob das ist ein Punkt zu seinen Gunsten oder gegen ist ... argumentative am besten.

Donal Fellows
quelle
5

Das breaknach dem Einschalten cases wird verwendet , um den Durchfall in den Switch - Anweisungen zu vermeiden. Interessanterweise kann dies jetzt durch die neu gebildeten Switch-Labels erreicht werden, die über JEP-325 implementiert wurden .

Mit diesen Änderungen kann das breakmit jedem Schalter casevermieden werden, wie weiter gezeigt wird:

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");
        }
    }
}

Bei Ausführung des obigen Codes mit JDK-12 könnte die Vergleichsausgabe als angesehen werden

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

und

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

und natürlich die Sache unverändert

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

Sie müssen den Code also nicht wiederholen, wenn Sie mehrere Fälle benötigen, um dasselbe zu tun:

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

Oder Sie können Dinge tun wie:

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

In einer Kaskadenart.

Wirklich fehleranfällig, wenn Sie mich fragen.

Francisco Soto
quelle
läuft das beides do thisund do thatdafür aber nur do thatdafür?
JonnyRaa
1
Lesen Sie einfach die Dokumente. Das ist schrecklich! Was für eine einfache Möglichkeit, Fehler zu schreiben!
JonnyRaa
4

Was die historischen Aufzeichnungen betrifft, hat Tony Hoare die Fallbeschreibung in den 1960er Jahren während der Revolution der "strukturierten Programmierung" erfunden. Tonys case-Anweisung unterstützte mehrere Labels pro Fall und das automatische Beenden ohne stinkende breakAnweisungen. Die Anforderung für eine explizite breakwar etwas, das aus der BCPL / B / C-Linie kam. Dennis Ritchie schreibt (in ACM HOPL-II):

Zum Beispiel war der Endfall, der einer BCPL-Switchon-Anweisung entgeht, nicht in der Sprache vorhanden, als wir ihn in den 1960er Jahren lernten, und daher ist die Überladung des Schlüsselworts break, um der B- und C-switch-Anweisung zu entkommen, eher auf eine unterschiedliche Entwicklung als auf ein Bewusstsein zurückzuführen Veränderung.

Ich konnte keine historischen Schriften über BCPL finden, aber Ritchies Kommentar legt nahe, dass dies breakmehr oder weniger ein historischer Unfall war. BCPL hat das Problem später behoben, aber vielleicht waren Ritchie und Thompson zu beschäftigt, Unix zu erfinden, um sich mit einem solchen Detail zu beschäftigen :-)

Norman Ramsey
quelle
Dies sollte mehr Stimmen bekommen. Anscheinend wusste das OP bereits, dass das Weglassen breakdie Ausführung von "mehreren Codeblöcken" ermöglicht, und befasst sich mehr mit der Motivation dieser Entwurfswahl. Andere erwähnten das bekannte Erbe von C bis Java, und diese Antwort trieb die Forschung noch weiter in die Zeit vor C. Ich wünschte, wir hätten dieses (wenn auch sehr primitive) Muster von Anfang an.
wlnirvana
3

Java leitet sich von C ab, dessen Erbe eine als Duff's Device bekannte Technik umfasst . Es ist eine Optimierung, die auf der Tatsache beruht, dass die Kontrolle von einem Fall zum nächsten fällt, wenn keine break;Aussage vorliegt. Zu der Zeit, als C standardisiert wurde, gab es eine Menge solchen Codes "in the wild", und es wäre kontraproduktiv gewesen, die Sprache zu ändern, um solche Konstruktionen zu brechen.

Jim Lewis
quelle
1

Wie die Leute bereits sagten, ist es ein Durchfallen und es ist kein Fehler, es ist eine Funktion. Wenn Sie zu viele breakAnweisungen stören, können Sie sie leicht entfernen, indem Sie stattdessen returnAnweisungen verwenden. Dies ist eigentlich eine gute Vorgehensweise, da Ihre Methoden so klein wie möglich sein sollten (aus Gründen der Lesbarkeit und Wartbarkeit), sodass eine switchAnweisung bereits groß genug für eine Methode ist. Daher sollte eine gute Methode nichts anderes enthalten ein Beispiel:

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");
        }        
    }
}   

Die Ausführung druckt:

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

wie erwartet.

Víctor Gil
quelle
0

Wenn der Compiler keine automatische Unterbrechung hinzufügt, können Sie einen Schalter / Fall verwenden, um auf Bedingungen zu testen, z. B. 1 <= a <= 3indem Sie die Unterbrechungsanweisung von 1 und 2 entfernen.

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;
}
Soufiane Hassou
quelle
Yecch. Ich hasse das total.
Ncmathsadist
0

weil es Situationen gibt, in denen Sie beispielsweise durch den ersten Block fließen möchten, um zu vermeiden, dass derselbe Code in mehrere Blöcke geschrieben wird, diese aber dennoch für die mroe-Steuerung aufgeteilt werden können. Es gibt auch eine Menge anderer Gründe.

Jonas B.
quelle
0

Es ist eine alte Frage, aber tatsächlich bin ich heute auf die Verwendung des Falls ohne Unterbrechung gestoßen. Die Nichtverwendung von break ist tatsächlich sehr nützlich, wenn Sie verschiedene Funktionen nacheinander kombinieren müssen.

Beispiel: Verwendung von http-Antwortcodes zur Authentifizierung des Benutzers mit einem Zeit-Token

Server-Antwortcode 401 - Token ist veraltet -> Token neu
generieren und Benutzer anmelden. Server-Antwortcode 200 - Token ist in Ordnung -> Benutzer anmelden.

in case Aussagen:

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;
        }

Auf diese Weise müssen Sie die Anmeldebenutzerfunktion für die 401-Antwort nicht aufrufen, da die Laufzeit bei der Neuerstellung des Tokens in den Fall 200 springt.

Vladimír Gašpar
quelle
0

Sie können leicht andere Arten von Zahlen, Monaten und Zählungen trennen.
Dies ist besser als in diesem Fall;

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;
     }
 }
John Dev
quelle
0

Ich arbeite jetzt an einem Projekt, das ich breakin meiner switch-Anweisung benötige , sonst funktioniert der Code nicht. Nehmen Sie mich mit und ich werde Ihnen ein gutes Beispiel dafür geben, warum Sie es breakin Ihrer switch-Anweisung benötigen .

Stellen Sie sich vor, Sie haben drei Zustände, einen, der darauf wartet, dass der Benutzer eine Zahl eingibt, den zweiten, um sie zu berechnen, und den dritten, um die Summe zu drucken.

In diesem Fall haben Sie:

  1. Status1 - Warten Sie, bis der Benutzer eine Nummer eingegeben hat
  2. State2 - Druckt die Summe aus
  3. state3 - Berechne die Summe

Wenn Sie sich die Zustände ansehen , möchten Sie, dass die Reihenfolge der Genauigkeit bei Zustand1 , dann bei Zustand3 und schließlich bei Zustand2 beginnt . Andernfalls drucken wir nur Benutzereingaben, ohne die Summe zu berechnen. Um es noch einmal zu verdeutlichen, warten wir, bis der Benutzer einen Wert eingibt, berechnen dann die Summe und drucken die Summe aus.

Hier ist ein Beispielcode:

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;
    }
}

Wenn wir nicht verwenden break, wird es in dieser Reihenfolge ausgeführt, state1 , state2 und state3 . Mit verwenden breakwir dieses Szenario jedoch und können in der richtigen Prozedur bestellen, die mit state1, dann state3 und nicht zuletzt state2 beginnt.

Dler
quelle
-1

Genau, weil Sie mit einer cleveren Platzierung Blöcke in Kaskade ausführen können.

Hellektor
quelle