И 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 }
И тогда необходимость вставлять операторы прерывания снова становится потенциальной неприятностью, ловушкой для неосторожных, а не чем-то действительно полезным.
Я надеюсь, что это дало вам лучшее представление о провале корпуса выключателя. Тем не менее, я готов поспорить, что вы забудете о провалах в какой-то момент в будущем. Я сам то и дело забываю об этом.