И Scala, и Kotlin основаны на Java, и оба обладают многими функциями, которые знакомы программистам на C и C ++, например операторами if, а также циклами while и do-while.

Но не операторы переключения регистра. Изобретатели как Scala, так и Kotlin хотели функциональности switch-case, но без семантики провалов, которая сбивает с толку многих программистов.

Рассмотрим этот игрушечный пример оператора switch-case:

        String userColorChoice = getUserColorChoice();
        switch (userColorChoice) {
            case "red":
                System.out.println("You chose red");
                setColor(255, 0, 0);
            case "green":
                System.out.println("You chose green");
                setColor(0, 255, 0);
            case "blue":
                System.out.println("You chose blue");
                setColor(0, 0, 255);
            default:
                System.out.println("Substituting black...");
                setColor(0, 0, 0);
        }

В этом примере нам действительно не нужно знать, как getUserColorChoice() работает, и нам не важно, что setColor() устанавливает цвет.

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

Но ... затем он установит цвет на зеленый, затем на синий и, наконец, на черный. На консоли пользователь увидит это:

You chose red
You chose green
You chose blue
Substituting black...

Ясно, что произошло не то, что мы хотели.

Это провал корпуса выключателя. Мы предотвращаем это с помощью прерываний, которые отправляют выполнение за пределы оператора switch-case, пропуская случаи, которые не применяются. Пример игрушки будет изменен следующим образом:

        switch (userColorChoice) {
            case "red":
                System.out.println("You chose red");
                setColor(255, 0, 0);
                break;
            case "green":
                System.out.println("You chose green");
                setColor(0, 255, 0);
                break;
            case "blue":
                System.out.println("You chose blue");
                setColor(0, 0, 255);
                break;
            default:
                System.out.println("Substituting black");
                setColor(0, 0, 0);
        }

Итак, если пользователь выбирает «красный», программа устанавливает красный цвет в качестве цвета, и прерывание отправляет выполнение программы тому, что находится за пределами оператора switch-case. Для случая по умолчанию перерыв не требуется.

Please enter a color: red
You chose red

Некоторые из вас могут задаться вопросом: почему компилятор не может просто вывести паузу в конце каждого случая? А остальные из вас могут знать, что падение корпуса выключателя полезно. Иногда. Но, может быть, обычно и нет.

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

        String userColorChoice = getUserColorChoice();
        switch (userColorChoice) {
            case "Red":
            case "red":
                System.out.println("You chose red");
                setColor(255, 0, 0);
                break;
            case "Green":
            case "green":
                System.out.println("You chose green");
                setColor(0, 255, 0);
                break;
            case "Blue":
            case "blue":
                System.out.println("You chose blue");
                setColor(0, 0, 255);
                break;
            default:
                System.out.println("Substituting black");
                setColor(0, 0, 0);
        }

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

На более низком уровне switch-case может быть реализован с помощью операторов if и goto. Вот иллюстрация на каком-то БЕЙСИКЕ:

520 IF CHOICE == "Red" GOTO 530
521 IF CHOICE == "red" GOTO 531
522 IF CHOICE == "Green" GOTO 535
523 IF CHOICE == "green" GOTO 536
524 IF CHOICE == "Blue" GOTO 540
525 IF CHOICE == "blue" GOTO 541
526 GOTO 545
530 REM Red CASE
531 REM red CASE
532 PRINT "You chose red"
533 GOSUB SETCOLOR(255, 0, 0)
534 GOTO 550
535 REM Green CASE
536 REM green CASE
537 PRINT "You chose green"
538 GOSUB SETCOLOR(0, 255, 0)
539 GOTO 550
540 REM BLUE CASE
541 REM blue CASE
542 PRINT "You chose blue"
543 GOSUB SETCOLOR(0, 0, 255)
544 GOTO 550
545 REM DEFAULT
546 PRINT "Substituting black"
547 GOSUB SETCOLOR(0, 0, 0)
550 REM GET ON WITH THE REST OF THE PROGRAM

Так же, как в случае переключения Java по умолчанию не требуется разрыв, так и для приведенного выше вида BASIC не требуется GOTO 550 в строке 548, потому что нет строки 549, которую можно было бы пропустить.

Однако и FORTRAN, и BASIC имеют операторы select-case, которые работают почти так же, как switch-case, но без провалов.

Используя программу javap в соответствующем файле *.class, мы видим, что действительно с инструкциями goto компилятор Java реализует операторы прерывания для виртуальной машины Java (JVM).

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

Я назвал свой класс Java FallThroughDemo. Я объявил getUserColorChoice() и setColor() частными, и по умолчанию программа javap не показывает ничего, помеченного как частные.

Без операторов break в байт-коде есть только две инструкции goto, но они выполняются перед строками, соответствующими отдельным случаям. Выход javap был изменен только для отступов и интервалов.

public class basicexercises.FallThroughDemo {
  public basicexercises.FallThroughDemo();
    Code:
       0: aload_0
       1: invokespecial #1 // Method java/lang/Object."<init>":
()V
       4: return
public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #15 
                   // Method getUserColorChoice:()Ljava/lang/String;
       3: astore_1
       4: aload_1
       5: astore_2
       6: iconst_m1
       7: istore_3
       8: aload_2
       9: invokevirtual #16 // Method java/lang/String.hashCode:()I
      12: lookupswitch  { // 3
                112785: 48
               3027034: 76
              98619139: 62
               default: 87
          }
      48: aload_2
      49: ldc           #17                 // String red
      51: invokevirtual #18
            // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      54: ifeq          87
      57: iconst_0
      58: istore_3
      59: goto          87
      62: aload_2
      63: ldc           #19                 // String green
      65: invokevirtual #18
            // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      68: ifeq          87
      71: iconst_1
      72: istore_3
      73: goto          87
      76: aload_2
      77: ldc           #20                 // String blue
      79: invokevirtual #18 
             // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      82: ifeq          87
      85: iconst_2
      86: istore_3
      87: iload_3
      88: tableswitch   { // 0 to 2
                     0: 116
                     1: 132
                     2: 148
               default: 164
          }
     116: getstatic     #5
                 // Field java/lang/System.out:Ljava/io/PrintStream;
     119: ldc           #21                 // String You chose red
     121: invokevirtual #22 
         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     124: sipush        255
     127: iconst_0
     128: iconst_0
     129: invokestatic  #23           // Method setColor:(III)V
     132: getstatic     #5 
                 // Field java/lang/System.out:Ljava/io/PrintStream;
     135: ldc           #24               // String You chose green
     137: invokevirtual #22
         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     140: iconst_0
     141: sipush        255
     144: iconst_0
     145: invokestatic  #23               // Method setColor:(III)V
     148: getstatic     #5
                 // Field java/lang/System.out:Ljava/io/PrintStream;
     151: ldc           #25                 // String You chose blue
     153: invokevirtual #22
        // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     156: iconst_0
     157: iconst_0
     158: sipush        255
     161: invokestatic  #23               // Method setColor:(III)V
     164: getstatic     #5 
                 // Field java/lang/System.out:Ljava/io/PrintStream;
     167: ldc           #26         // String Substituting black...
     169: invokevirtual #22 
         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     172: iconst_0
     173: iconst_0
     174: iconst_0
     175: invokestatic  #23               // Method setColor:(III)V
     178: return
}

Поэтому, если пользователь выбирает «красный», выполняются строки со 116 по 129. Но затем он переходит к строке 132, которая является началом «зеленого» дела. А затем «синий» корпус и черный по умолчанию.

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

      88: tableswitch   { // 0 to 2
                     0: 116
                     1: 135
                     2: 154
               default: 173
          }
     116: getstatic     #5 
                 // Field java/lang/System.out:Ljava/io/PrintStream;
     119: ldc           #21                 // String You chose red
     121: invokevirtual #22
         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     124: sipush        255
     127: iconst_0
     128: iconst_0
     129: invokestatic  #23               // Method setColor:(III)V
     132: goto          187
     135: getstatic     #5 
                 // Field java/lang/System.out:Ljava/io/PrintStream;
     138: ldc           #24               // String You chose green
     140: invokevirtual #22
         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     143: iconst_0
     144: sipush        255
     147: iconst_0
     148: invokestatic  #23               // Method setColor:(III)V
     151: goto          187
     154: getstatic     #5 
                 // Field java/lang/System.out:Ljava/io/PrintStream;
     157: ldc           #25                 // String You chose blue
     159: invokevirtual #22  
         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     162: iconst_0
     163: iconst_0
     164: sipush        255
     167: invokestatic  #23               // Method setColor:(III)V
     170: goto          187
     173: getstatic     #5 
                 // Field java/lang/System.out:Ljava/io/PrintStream;
     176: ldc           #26         // String Substituting black...
     178: invokevirtual #22      
         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     181: iconst_0
     182: iconst_0
     183: iconst_0
     184: invokestatic  #23               // Method setColor:(III)V
     187: return
}

Итак, теперь, если пользователь выбирает «красный», строки со 116 по 129 выполняются, как и раньше.

Но строка 132 - это оператор goto, который заставляет выполнение программы перескакивать на строку 187, которая находится за пределами оператора switch и, в этом игрушечном примере, оказывается концом программы.

Благодаря переходу на использование заглавных названий цветов размер файла байт-кода увеличивается до более чем 250 строк.

      12: lookupswitch  { // 6
                 82033: 72
                112785: 86
               2073722: 128
               3027034: 142
              69066467: 100
              98619139: 114
               default: 153
          }
      72: aload_2
      73: ldc           #17                 // String Red
      75: invokevirtual #18
             // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      78: ifeq          153
      81: iconst_0
      82: istore_3
      83: goto          153
      86: aload_2
      87: ldc           #19                 // String red
      89: invokevirtual #18 
             // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      92: ifeq          153
      95: iconst_1
      96: istore_3
      97: goto          153
... omitting the lines for Green, green, Blue, blue...
     153: iload_3
     154: tableswitch   { // 0 to 5
                     0: 192
                     1: 192
                     2: 211
                     3: 211
                     4: 230
                     5: 230
               default: 249
          }
     192: getstatic     #5 
                 // Field java/lang/System.out:Ljava/io/PrintStream;
     195: ldc           #24                 // String You chose red
     197: invokevirtual #25 
        // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     200: sipush        255
     203: iconst_0
     204: iconst_0
     205: invokestatic  #26                 // Method setColor:(III)V
     208: goto          263
     211: getstatic     #5 
                 // Field java/lang/System.out:Ljava/io/PrintStream;
     214: ldc           #27               // String You chose green
...you get the idea...
     243: invokestatic  #26                // Method setColor:(III)V
     246: goto          263
     249: getstatic     #5 
                 // Field java/lang/System.out:Ljava/io/PrintStream;
     252: ldc           #29          // String Substituting black...
     254: invokevirtual #25 
         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     257: iconst_0
     258: iconst_0
     259: iconst_0
     260: invokestatic  #26                // Method setColor:(III)V
     263: return
}

Что, если пользователь вводит что-то вроде «КРАСНЫЙ» или «ЗЕЛЕНЫЙ»? На самом деле нет смысла загружать наш оператор switch-case всеми возможными входными данными, которые мы можем предвидеть, когда мы можем просто применить toLowerCase() (или, если хотите, toUpperCase()), чтобы у нас был меньший, более управляемый набор возможных вариантов иметь дело с.

        switch (userColorChoice.toLowerCase()) {
            case "red":
                System.out.println("You chose red");
                setColor(255, 0, 0);
                break;
            case "green":
                System.out.println("You chose green");
                setColor(0, 255, 0);
                break;
            case "blue":
                System.out.println("You chose blue");
                setColor(0, 0, 255);
                break;
            default:
                System.out.println("Substituting black");
                setColor(0, 0, 0);
        }

Это фактически приводит к упрощению результирующего байт-кода, возвращая его к 187 строкам:

public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #15 
                // Method getUserColorChoice:()Ljava/lang/String;
       3: astore_1
       4: aload_1
       5: invokevirtual #16  
         // Method java/lang/String.toLowerCase:()Ljava/lang/String;
       8: astore_2
       9: iconst_m1
      10: istore_3
      11: aload_2
      12: invokevirtual #17 .  // Method java/lang/String.hashCode:
()I
      15: lookupswitch  { // 3
                112785: 48
               3027034: 76
              98619139: 62
               default: 87
          }
      48: aload_2
      49: ldc           #18                 // String red
      51: invokevirtual #19 
             // Method java/lang/String.equals:(Ljava/lang/Object;)Z
...omitting a few lines...
      87: iload_3
      88: tableswitch   { // 0 to 2
                     0: 116
                     1: 135
                     2: 154
               default: 173
          }
     116: getstatic     #5 
                 // Field java/lang/System.out:Ljava/io/PrintStream;
     119: ldc           #22                 // String You chose red
     121: invokevirtual #23 
         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     124: sipush        255
     127: iconst_0
     128: iconst_0
     129: invokestatic  #24                // Method setColor:(III)V
     132: goto          187
...omitting a few more lines...
     176: ldc           #27          // String Substituting black...
     178: invokevirtual #23 
         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     181: iconst_0
     182: iconst_0
     183: iconst_0
     184: invokestatic  #24                // Method setColor:(III)V
     187: return
}

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

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