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?
Odpowiedzi:
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ć.
// 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.
case
s jest ułożonych w ten sposób. Jeśli między nimi jest kod, to tak, komentarz jest prawdopodobnie zasłużony.
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 )
Historycznie , to dlatego, że case
był zasadniczo wyznaczającą label
, znany również jako punktu docelowego z goto
rozmowy. 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 break
prawie zawsze jest to domyślne zachowanie, które wolałbyś mieć na końcu każdej sprawy.
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);
}
}
Myślę, że to pomyłka. Jako konstrukcję językową można ją równie łatwo mieć break
jako domyślną i zamiast tego mieć fallthrough
słowo kluczowe. Większość kodu, który napisałem i przeczytałem, ma przerwę po każdej sprawie.
continue <case name>
co pozwala jednoznacznie określić, z którą instrukcją przypadku kontynuować;
case
w ramach prądu switch
, po prostu staje się goto
. ;-)
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 break
stwierdzeniu na końcu sprawy i spowodować nieoczekiwane zachowanie. Dobre kompilatory ostrzeżą Cię, gdy pominiesz instrukcję break.
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 goto
sł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.
break
Po włączeniu case
s 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ąć break
każdego przełącznika, case
jak 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
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.
do this
i do that
dla tego, ale tylko do that
dla tego?
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 break
oświadczeń. Wymóg wyraźnego break
był 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 break
był 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 :-)
break
umoż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.
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.
Jak powiedzieli wcześniej ludzie, ma to na celu umożliwienie przebicia i nie jest to błąd, to cecha. Jeśli break
denerwuje Cię zbyt wiele instrukcji, możesz łatwo się ich pozbyć, używając return
zamiast 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 switch
instrukcja 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.
Brak automatycznego przerwania dodanego przez kompilator umożliwia użycie przełącznika / przypadku do testowania warunków, takich jak 1 <= a <= 3
usunię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;
}
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.
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;
}
}
Teraz pracuję nad projektem, w którym potrzebuję break
w mojej instrukcji przełącznika, w przeciwnym razie kod nie zadziała. Trzymaj się ze mną, a dam ci dobry przykład tego, dlaczego potrzebujesz break
w 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:
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.
break
.