Dlaczego potrzebujemy przerwy po opisach przypadków?

94

Dlaczego kompilator nie umieszcza automatycznie instrukcji break po każdym bloku kodu w przełączniku? Czy to z powodów historycznych? Kiedy chciałbyś wykonać wiele bloków kodu?

unj2
źródło
2
Udzielono odpowiedzi na temat JDK-12 i zmieniono etykiety przełączników, aby nie były wymagane break.
Naman

Odpowiedzi:

94

Czasami pomocne jest powiązanie wielu przypadków z tym samym blokiem kodu, na przykład

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

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

itd. Tylko przykład.

Z mojego doświadczenia wynika, że ​​zazwyczaj „upadek” i wykonywanie wielu bloków kodu w jednym przypadku jest złym stylem, ale w niektórych sytuacjach może się to przydać.

WildCrustacean
źródło
29
Po prostu zawsze dodawaj komentarz wzdłuż linii, // Intentional fallthrough.gdy pominiesz przerwę. Moim zdaniem to nie tyle zły styl, ile „łatwo przez przypadek zapomnieć o przerwie”. PS Oczywiście nie w prostych przypadkach, jak w samej odpowiedzi.
podwoić
@doublep - zgadzam się. Moim zdaniem unikałbym tego, jeśli to możliwe, ale jeśli ma to sens, upewnij się, że jest bardzo jasne, co robisz.
WildCrustacean
6
@doublep: Nie zawracałbym sobie głowy komentarzem, jeśli wiele cases jest ułożonych w ten sposób. Jeśli między nimi jest kod, to tak, komentarz jest prawdopodobnie zasłużony.
Billy ONeal
4
Wyobrażam sobie język, w którym można zadeklarować wiele przypadków w jednym case, tak jak to: case 'A','B','C': doSomething(); case 'D','E': doSomethingElse();bez potrzeby przerwy między nimi. Pascal mógłby to zrobić: „Instrukcja case porównuje wartość wyrażenia porządkowego do każdego selektora, który może być stałą, podzakresem lub listą oddzielonych przecinkami”. ( wiki.freepascal.org/Case )
Christian Semrau
32

Historycznie , to dlatego, że casebył zasadniczo wyznaczającą label, znany również jako punktu docelowego z gotorozmowy. Instrukcja switch i związane z nią przypadki tak naprawdę reprezentują gałąź wielościeżkową z wieloma potencjalnymi punktami wejścia do strumienia kodu.

Wszystko to powiedziawszy, zauważono prawie nieskończoną liczbę razy, że breakprawie zawsze jest to domyślne zachowanie, które wolałbyś mieć na końcu każdej sprawy.

Bob Cross
źródło
30

Java pochodzi z C i taka jest składnia z C.

Czasami chcesz, aby wiele instrukcji case miało tylko jedną ścieżkę wykonywania. Poniżej znajduje się próbka, która powie Ci, ile dni w miesiącu.

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
źródło
4
Ktoś wycelował w strzałkę w górę i chybił? A może mieli wołowinę z twoim stylem klamry lub wcięciem ...
Jim Lewis
Nie wiem, więc daj mi +1. To jest przykład, w którym rozwiązywanie pomaga, chociaż naprawdę chciałbym, aby Java wybrała bardziej nowoczesne stwierdzenie przypadku. Narzędzie Cobol EVALUATE-WHEN-OTHERWISE jest znacznie potężniejsze i wyprzedza Javę. Wyrażenie dopasowania Scali to nowoczesny przykład tego, co można zrobić.
Jim Ferrans,
1
Moi uczniowie zostaliby za to publicznie wychłostani. To brzydki kojot.
ncmathsadist
2
@ncmathsadist To ilustruje punkt dotyczący jednego sposobu zrobienia czegoś. Nie przeczę, że ten przykład jest prawdopodobnie skrajny. Ale jest to przykład z prawdziwego świata, który, jak sądzę, pomaga ludziom zrozumieć koncepcję.
Romain Hippeau
15

Myślę, że to pomyłka. Jako konstrukcję językową można ją równie łatwo mieć breakjako domyślną i zamiast tego mieć fallthroughsłowo kluczowe. Większość kodu, który napisałem i przeczytałem, ma przerwę po każdej sprawie.

Prezydent James K. Polk
źródło
4
Raczej zasugerowałbym, continue <case name>co pozwala jednoznacznie określić, z którą instrukcją przypadku kontynuować;
Vilx-
4
@Vilx Gdy zezwalasz na dowolny casew ramach prądu switch, po prostu staje się goto. ;-)
Christian Semrau
13

Możesz robić wiele interesujących rzeczy dzięki upadkowi obudowy.

Na przykład, powiedzmy, że chcesz wykonać określoną akcję dla wszystkich przypadków, ale w pewnym przypadku chcesz wykonać tę akcję plus coś innego. Użycie instrukcji switch z fall-through uczyniłoby to całkiem prostym.

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

Oczywiście łatwo jest zapomnieć o breakstwierdzeniu na końcu sprawy i spowodować nieoczekiwane zachowanie. Dobre kompilatory ostrzeżą Cię, gdy pominiesz instrukcję break.

Zach Johnson
źródło
Czy można użyć przełącznika / wielkości liter w łańcuchach w Javie?
Steve Kuo
@Steve: Ups, chyba nie w tej chwili. Zgodnie ze stackoverflow.com/questions/338206/… , łańcuchy znaków będą dozwolone w przyszłej wersji Java. (Obecnie większość programowania wykonuję w języku C #, co zezwala na ciągi znaków w instrukcjach przełącznika). Edytowałem odpowiedź, aby usunąć wprowadzające w błąd cytaty.
Zach Johnson
2
@ZachJohnson, znacznie później, Java 7 pozwala na włączanie ciągów znaków.
Bob Cross,
7

Dlaczego kompilator nie umieszcza automatycznie instrukcji break po każdym bloku kodu w przełączniku?

Pomijając dobre pragnienie, aby móc używać identycznego bloku w kilku przypadkach (które mogą być w specjalnych przypadkach) ...

Czy to z powodów historycznych? Kiedy chciałbyś wykonać wiele bloków kodu?

Dotyczy to głównie zgodności z C i prawdopodobnie jest to starożytny hack z czasów, gdy gotosłowa kluczowe wędrowały po ziemi. To ma umożliwić niesamowite rzeczy, oczywiście, takie jak mechanizm duffa , ale czy to jest punkt na jego korzyść lub przeciwko jest ... kłótliwy w najlepsze.

Donal Fellows
źródło
5

breakPo włączeniu cases służy do uniknięcia fallthrough w sprawozdaniu przełącznika. Co ciekawe, można to teraz osiągnąć dzięki nowo utworzonym etykietom przełączników zaimplementowanym w JEP-325 .

Dzięki tym zmianom można uniknąć breakkażdego przełącznika, casejak pokazano dalej: -

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

Po wykonaniu powyższego kodu za pomocą JDK-12 , porównawcze dane wyjściowe mogą być postrzegane jako

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

i

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

i oczywiście rzecz bez zmian

// input
3
many // default case match
many // branches to 'default' as well
Naman
źródło
4

Nie musisz więc powtarzać kodu, jeśli potrzebujesz kilku przypadków, aby zrobić to samo:

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

Możesz też:

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

W sposób kaskadowy.

Naprawdę podatne na błędy / zamieszanie, jeśli o mnie chodzi.

Francisco Soto
źródło
czy to działa zarówno do thisi do thatdla tego, ale tylko do thatdla tego?
JonnyRaa
1
po prostu przeczytaj dokumentację. To okropne! Cóż za łatwy sposób na pisanie błędów!
JonnyRaa
4

Jeśli chodzi o zapis historyczny, Tony Hoare wynalazł opis przypadku w latach sześćdziesiątych XX wieku, podczas rewolucji „programowania strukturalnego”. Oświadczenie Tony'ego obsługiwało wiele etykiet na sprawę i automatyczne wyjście bez śmierdzących breakoświadczeń. Wymóg wyraźnego breakbył czymś, co wyszło z linii BCPL / B / C. Dennis Ritchie pisze (w ACM HOPL-II):

Na przykład przypadek końcowy, który wymyka się z instrukcji przełączania BCPL, nie był obecny w języku, gdy uczyliśmy się go w latach sześćdziesiątych XX wieku, więc przeciążenie słowa kluczowego break, aby uciec od instrukcji przełączania B i C, zawdzięczamy raczej rozbieżnej ewolucji niż świadomości zmiana.

Nie udało mi się znaleźć żadnych zapisów historycznych na temat BCPL, ale komentarz Ritchiego sugeruje, że breakbył to mniej więcej historyczny wypadek. BCPL później naprawił problem, ale być może Ritchie i Thompson byli zbyt zajęci wymyślaniem Uniksa, aby zawracać sobie głowę takim szczegółem :-)

Norman Ramsey
źródło
Powinno to uzyskać więcej głosów. Najwyraźniej OP wiedział już, że pomijanie breakumożliwia „wykonanie wielu bloków kodu” i bardziej interesuje go motywacja tego wyboru projektu. Inni wspominali o dobrze znanym dziedzictwie od C do Java, a ta odpowiedź posunęła badania jeszcze dalej, do czasów sprzed C. Żałuję, że nie mamy tego (choć bardzo prymitywnego) dopasowania wzorców od samego początku.
wlnirvana
3

Java wywodzi się z języka C, którego dziedzictwo obejmuje technikę znaną jako urządzenie Duffa . Jest to optymalizacja polegająca na fakcie, że kontrola przechodzi z jednego przypadku do drugiego w przypadku braku break;instrukcji. Do czasu ujednolicenia języka C było mnóstwo takiego kodu „na wolności” i zmiana języka w celu złamania takich konstrukcji przyniosłaby efekt przeciwny do zamierzonego.

Jim Lewis
źródło
2

Jak powiedzieli wcześniej ludzie, ma to na celu umożliwienie przebicia i nie jest to błąd, to cecha. Jeśli breakdenerwuje Cię zbyt wiele instrukcji, możesz łatwo się ich pozbyć, używając returnzamiast nich instrukcji. Jest to właściwie dobra praktyka, ponieważ twoje metody powinny być tak małe, jak to tylko możliwe (ze względu na czytelność i łatwość utrzymania), więc switchinstrukcja jest już wystarczająco duża dla metody, dlatego dobra metoda nie powinna zawierać niczego więcej, to jest przykład:

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

Wykonanie drukuje:

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

zgodnie z oczekiwaniami.

Víctor Gil
źródło
0

Brak automatycznego przerwania dodanego przez kompilator umożliwia użycie przełącznika / przypadku do testowania warunków, takich jak 1 <= a <= 3usunięcie instrukcji break z 1 i 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;
}
Soufiane Hassou
źródło
Yecch. Całkowicie tego nienawidzę.
ncmathsadist
0

ponieważ są sytuacje, w których chcesz przepłynąć przez pierwszy blok, na przykład, aby uniknąć pisania tego samego kodu w wielu blokach, ale nadal możesz je podzielić w celu kontroli mroe. Jest też mnóstwo innych powodów.

Jonas B
źródło
0

To stare pytanie, ale w rzeczywistości spotkałem się dzisiaj z używaniem instrukcji bez przerwy. Brak używania break jest w rzeczywistości bardzo przydatny, gdy trzeba połączyć różne funkcje po kolei.

np. użycie kodów odpowiedzi http do uwierzytelnienia użytkownika za pomocą tokena czasowego

kod odpowiedzi serwera 401 - token jest nieaktualny -> ponownie wygeneruj token i zaloguj użytkownika.
Kod odpowiedzi serwera 200 - token jest OK -> zaloguj się użytkownika.

w przypadku oświadczeń:

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

Korzystając z tego, nie musisz wywoływać funkcji logowania użytkownika w celu uzyskania odpowiedzi 401, ponieważ po ponownym wygenerowaniu tokenu środowisko wykonawcze przechodzi do przypadku 200.

Vladimír Gašpar
źródło
0

Możesz łatwo oddzielić inny typ liczby, miesiąca, liczby.
Tak jest lepiej, jeśli w tym przypadku;

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
źródło
0

Teraz pracuję nad projektem, w którym potrzebuję breakw mojej instrukcji przełącznika, w przeciwnym razie kod nie zadziała. Trzymaj się ze mną, a dam ci dobry przykład tego, dlaczego potrzebujesz breakw oświadczeniu o przełączniku.

Wyobraź sobie, że masz trzy stany, jeden, który czeka, aż użytkownik wprowadzi liczbę, drugi do obliczenia, a trzeci do wydrukowania sumy.

W takim przypadku masz:

  1. State1 - Poczekaj, aż użytkownik wprowadzi liczbę
  2. State2 - wydrukuj sumę
  3. state3 - Oblicz sumę

Patrząc na stany, chciałbyś, aby kolejność egzekwowania zaczynała się od stan1 , następnie stan3 i na końcu stan2 . W przeciwnym razie wydrukujemy tylko dane wejściowe użytkowników bez obliczania sumy. Aby jeszcze raz to wyjaśnić, czekamy, aż użytkownik wprowadzi wartość, a następnie obliczamy sumę i wypisuje ją.

Oto przykładowy kod:

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

Jeśli nie używamy break, będzie wykonywał się w tej kolejności, stan1 , stan2 i stan3 . Ale używając break, unikamy tego scenariusza i możemy uporządkować we właściwej procedurze, która zaczyna się od state1, następnie state3 i na końcu state2.

Dler
źródło
-1

Dokładnie, ponieważ dzięki sprytnemu rozmieszczeniu możesz wykonywać bloki w kaskadzie.

Hellektor
źródło