Зачем нужны операторы break после case?

94

Почему компилятор не помещает автоматически операторы break после каждого блока кода в переключателе? По историческим причинам? Когда вы хотите, чтобы выполнялось несколько блоков кода?

unj2
источник
2
Сделал ответ о JDK-12 и изменил метки переключателей, чтобы не указывать break.
Наман

Ответы:

94

Иногда полезно иметь несколько вариантов, связанных с одним и тем же блоком кода, например

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

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

и т.д. Просто пример.

По моему опыту, обычно плохой стиль - «проваливаться» и выполнять несколько блоков кода для одного случая, но в некоторых ситуациях это может быть использовано.

Дикие ракообразные
источник
29
Просто всегда добавляйте комментарий вдоль строк, // Intentional fallthrough.когда пропускаете перерыв. На мой взгляд, это не столько плохой стиль, сколько «случайно забыть перерыв». PS Конечно не в простых случаях, как в самом ответе.
doublep
@doublep - согласен. На мой взгляд, я бы по возможности избегал этого, но если это имеет смысл, убедитесь, что вам очень ясно, что вы делаете.
WildCrustacean
6
@doublep: Я бы не стал заморачиваться с комментарием, если несколько cases складываются таким образом. Если между ними есть код, тогда да, комментарий, вероятно, заслуживает внимания.
Билли Онил
4
Я представляю язык, в котором вы можете объявить несколько вариантов в одном case, например:, case 'A','B','C': doSomething(); case 'D','E': doSomethingElse();без необходимости перерыва между вариантами. Паскаль мог это сделать: «Оператор case сравнивает значение порядкового выражения с каждым селектором, который может быть константой, поддиапазоном или их списком, разделенным запятыми». ( wiki.freepascal.org/Case )
Кристиан Семрау
32

Исторически сложился так , что это потому , что caseпо существу , определяющее направление label, также известное как целевая точка в виде gotoвызова. Оператор switch и связанные с ним случаи на самом деле просто представляют собой многостороннюю ветвь с несколькими потенциальными точками входа в поток кода.

Все это было замечено почти бесконечное количество раз, что breakпочти всегда является поведением по умолчанию, которое вы бы предпочли иметь в конце каждого случая.

Боб Кросс
источник
31

Java происходит от C, и это синтаксис от C.

Бывают случаи, когда вы хотите, чтобы несколько операторов case имели только один путь выполнения. Ниже приведен образец, который покажет вам, сколько дней в месяце.

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
Кто-то нацелился на стрелку вверх и промахнулся? Или, может быть, у них была говядина с вашим стилем или углублением ...
Джим Льюис
Не знаю, так что получи от меня +1. Это пример, когда провал помогает, хотя я действительно хотел бы, чтобы Java выбрала более современный оператор case. EVALUATE-WHEN-OTHERWISE от Cobol гораздо мощнее и предшествует Java. Выражение соответствия в Scala - это современный пример того, что можно сделать.
Джим Ферранс,
1
Моих учеников публично пороли за это. Это уродливый койот.
ncmathsadist
2
@ncmathsadist Это иллюстрирует один способ сделать что-либо. Я не возражаю, что этот пример, вероятно, экстремальный. Но это пример из реального мира, который, я считаю, помогает людям понять концепцию.
Romain Hippeau
15

Я считаю это ошибкой. В качестве языковой конструкции ее так же легко использовать breakпо умолчанию и вместо нее использовать fallthroughключевое слово. Большая часть кода, который я написал и прочитал, имеет перерыв после каждого случая.

Президент Джеймс К. Полк
источник
4
Я бы предпочел предложить, continue <case name>который позволяет явно указать, с каким оператором case продолжить;
Vilx-
4
@Vilx При разрешении произвольного значения в caseпределах тока switchэто просто становится goto. ;-)
Christian Semrau
13

С провалом футляра можно делать разные интересные вещи.

Например, допустим, вы хотите выполнить определенное действие для всех случаев, но в определенном случае вы хотите выполнить это действие плюс что-то еще. Использование оператора switch с провалом сделало бы это довольно легко.

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

Конечно, легко забыть breakутверждение в конце кейса и вызвать неожиданное поведение. Хорошие компиляторы предупредят вас, если вы опустите оператор break.

Зак Джонсон
источник
Можно ли использовать переключатель / регистр для строк в Java?
Стив Куо,
@ Стив: Ой, думаю, сейчас нет. Согласно stackoverflow.com/questions/338206/… , строки будут разрешены в будущей версии Java. (В настоящее время я занимаюсь большей частью своего программирования на C #, который позволяет использовать строки в операторах switch.) Я отредактировал ответ, чтобы удалить вводящие в заблуждение кавычки.
Зак Джонсон,
2
@ZachJohnson, намного позже Java 7 позволяет включать строки.
Боб Кросс
7

Почему компилятор не помещает автоматически операторы break после каждого блока кода в переключателе?

Не говоря уже о хорошем желании иметь возможность использовать один и тот же блок для нескольких случаев (которые могут быть особенными) ...

По историческим причинам? Когда вы хотите, чтобы выполнялось несколько блоков кода?

Это в основном для совместимости с C, и, возможно, это древний прием с тех времен, когда gotoключевые слова бродили по земле. Конечно, он делает возможным некоторые удивительные вещи, такие как Устройство Даффа , но, аргумент ли это за или против, в лучшем случае… аргумент.

Donal Fellows
источник
5

breakПосле того, как переключатель cases используется , чтобы избежать проваливается в отчетности коммутатора. Хотя, что интересно, теперь это может быть достигнуто с помощью недавно сформированных меток переключателей, реализованных через JEP-325 .

С этими изменениями можно избежать breakкаждого переключения case, как показано далее: -

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

На выполнение вышеуказанного кода с JDK-12 , то сравнительный выход можно рассматривать как

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

а также

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

и конечно же вещь без изменений

// input
3
many // default case match
many // branches to 'default' as well
Наман
источник
4

Таким образом, вам не нужно повторять код, если вам нужно несколько случаев, чтобы сделать одно и то же:

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

Или вы можете сделать что-то вроде:

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

Каскадным способом.

На самом деле подвержен ошибкам / путанице, если вы спросите меня.

Франсиско Сото
источник
это работает do thisи do thatдля этого, но только do thatдля этого?
JonnyRaa
1
просто прочтите документы. Это ужасно! Какой простой способ писать ошибки!
JonnyRaa
4

Судя по историческим данным, Тони Хоар изобрел утверждение случая в 1960-х годах, во время революции «структурного программирования». Оператор case Тони поддерживает несколько меток для каждого случая и автоматический выход без каких-либо вонючих breakоператоров. Требование явного характера breakисходило из линии BCPL / B / C. Деннис Ричи пишет (в ACM HOPL-II):

Например, конечный регистр, который ускользает из оператора переключения BCPL, отсутствовал в языке, когда мы изучали его в 1960-х годах, и поэтому перегрузка ключевого слова break для выхода из оператора переключения B и C является результатом расходящейся эволюции, а не сознательной изменение.

Мне не удалось найти никаких исторических писаний о BCPL, но комментарий Ричи предполагает, что это breakбыло более или менее исторической случайностью. Позднее BCPL устранил проблему, но, возможно, Ричи и Томпсон были слишком заняты изобретением Unix, чтобы беспокоиться о таких деталях :-)

Норман Рэмси
источник
Это должно получить больше голосов. По-видимому, OP уже знал, что пропуск breakпозволяет «выполнять несколько блоков кода», и его больше заботит мотивация этого выбора дизайна. Другие упомянули хорошо известное наследие от C до Java, и этот ответ подтолкнул исследование еще дальше к дням, предшествующим C. Хотелось бы, чтобы у нас с самого начала было это (хотя и очень примитивное) сопоставление с образцом.
wlnirvana
3

Java происходит от языка C, наследие которого включает технологию, известную как устройство Даффа . Это оптимизация, основанная на том факте, что контроль переходит от одного случая к другому в отсутствие break;оператора. К тому времени, когда C был стандартизирован, такого кода «в дикой природе» было достаточно, и было бы контрпродуктивно менять язык, чтобы разрушить такие конструкции.

Джим Льюис
источник
2

Как говорили раньше, это позволяет избежать провала, и это не ошибка, это особенность. Если breakвас раздражает слишком много утверждений, вы можете легко избавиться от них, используя returnвместо них инструкции. На самом деле это хорошая практика, потому что ваши методы должны быть как можно меньше (для удобства чтения и поддержки), поэтому switchоператор уже достаточно большой для метода, следовательно, хороший метод не должен содержать ничего другого, это пример:

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

На казнь печатается:

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

как и ожидалось.

Виктор Гил
источник
0

Отсутствие автоматического прерывания, добавленного компилятором, позволяет использовать переключатель / регистр для проверки условий, например, 1 <= a <= 3путем удаления оператора break из 1 и 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. Я полностью это ненавижу.
ncmathsadist
0

потому что есть ситуации, когда вы хотите пройти через первый блок, например, чтобы избежать написания одного и того же кода в нескольких блоках, но все же иметь возможность разделить их для управления mroe. Есть и масса других причин.

Йонас Б.
источник
0

Это старый вопрос, но на самом деле сегодня я столкнулся с использованием case без оператора break. На самом деле не использовать break очень полезно, когда вам нужно последовательно комбинировать разные функции.

например, использование кодов ответа http для аутентификации пользователя с помощью токена времени

код ответа сервера 401 - токен устарел -> повторно создать токен и войти в систему.
Код ответа сервера 200 - токен в порядке -> войти в систему.

в случае утверждения:

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

Используя это, вам не нужно вызывать пользовательскую функцию журнала для ответа 401, потому что, когда токен регенерируется, среда выполнения переходит к делу 200.

Владимир Гашпар
источник
0

Вы можете легко выделить другой тип числа, месяц, счет.
Это лучше, если в этом случае;

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

Сейчас я работаю над проектом, в котором мне нужен breakоператор switch, иначе код не будет работать. Потерпите меня, и я дам вам хороший пример того, зачем вам нужен breakоператор switch.

Представьте, что у вас есть три состояния: одно ожидает, пока пользователь введет число, второе - вычислит его, а третье - напечатает сумму.

В этом случае у вас есть:

  1. State1 - Подождите, пока пользователь введет число
  2. State2 - вывести сумму
  3. state3 - вычислить сумму

Глядя на состояния, вы бы хотели, чтобы порядок действий начинался с состояния1 , затем состояния3 и, наконец, состояния2 . В противном случае мы будем печатать только вводимые пользователем данные без вычисления суммы. Чтобы еще раз прояснить это, мы ждем, пока пользователь вводит значение, затем вычисляем сумму и распечатываем сумму.

Вот пример кода:

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

Если мы не используем break, он будет выполняться в следующем порядке: state1 , state2 и state3 . Но используя break, мы избегаем этого сценария и можем заказать правильную процедуру, которая должна начинаться с состояния1, затем состояния3 и последнего, но не менее важного состояния2.

Длер
источник
-1

Именно потому, что при некотором умном размещении вы можете выполнять блоки каскадом.

Hellektor
источник