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