【发布时间】:2011-02-12 05:02:18
【问题描述】:
为什么编译器不自动在 switch 中的每个代码块之后放置 break 语句?是因为历史原因吗?您希望何时执行多个代码块?
【问题讨论】:
-
Made an answer 关于 JDK-12 和开关标签已改组为不强制要求
break。
标签: java switch-statement case language-design break
为什么编译器不自动在 switch 中的每个代码块之后放置 break 语句?是因为历史原因吗?您希望何时执行多个代码块?
【问题讨论】:
break。
标签: java switch-statement case language-design break
在 switch cases 之后的 break 用于避免 switch 语句中的失败。虽然有趣的是,现在这可以通过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");
}
}
}
在executing the above code with 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
【讨论】:
我现在正在处理我在 switch 语句中需要 break 的项目,否则代码将无法工作。请耐心等待,我会给你一个很好的例子,说明为什么在你的 switch 语句中需要break。
假设您有三种状态,一种等待用户输入数字,第二种等待计算,第三种打印总和。
在这种情况下,你有:
查看状态,您可能希望从 state1 开始,然后是 state3,最后是 state2。否则我们将只打印用户输入而不计算总和。再次澄清一下,我们等待用户输入一个值,然后计算总和并打印总和。
这是一个示例代码:
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,我们可以避免这种情况,并且可以在正确的过程中进行排序,即从state1开始,然后是state3,最后是state2。
【讨论】:
正如人们之前所说,这是允许跌倒,这不是一个错误,它是一个特性。
如果太多的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
正如预期的那样。
【讨论】:
您可以轻松区分其他类型的数字、月份、计数。
如果在这种情况下,这会更好;
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;
}
}
【讨论】:
这是一个老问题,但实际上我今天遇到了使用 case without 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。
【讨论】:
你可以做各种有趣的事情。
例如,假设您想针对所有情况执行特定操作,但在某些情况下您想要执行该操作以及其他操作。使用带有 fall-through 的 switch 语句会很容易。
switch (someValue)
{
case extendedActionValue:
// do extended action here, falls through to normal action
case normalActionValue:
case otherNormalActionValue:
// do normal action here
break;
}
当然,很容易忘记在 case 结束时的 break 语句并导致意外行为。当你省略 break 语句时,好的编译器会警告你。
【讨论】:
就历史记录而言,Tony Hoare 在 1960 年代的“结构化编程”革命期间发明了案例陈述。 Tony 的 case 语句支持每个 case 多个标签和自动退出,没有臭的 break 语句。对显式 break 的要求来自 BCPL/B/C 行。 Dennis Ritchie 写道(在 ACM HOPL-II 中):
例如,语言中不存在从 BCPL switchon 语句转义的 endcase 当我们在 1960 年代学习它时,因此重载了 break 关键字以转义 从 B 和 C 的 switch 语句中产生的原因是发散进化而不是有意识的变化。
我还没有找到任何关于 BCPL 的历史著作,但 Ritchie 的评论表明 break 或多或少是一个历史事故。 BCPL 后来解决了这个问题,但也许 Ritchie 和 Thompson 都忙于发明 Unix,而无暇顾及这样的细节 :-)
【讨论】:
break 允许“执行多个代码块”,并且更关心这种设计选择的动机。其他人提到了从 C 到 Java 的著名遗产,这个答案将研究进一步推到了 C 之前的时代。我希望我们从一开始就有这种(虽然非常原始)模式匹配。
Historically,这是因为case 本质上定义了label,也称为target point 的goto 调用。 switch 语句及其相关案例实际上只是代表了一个多路分支,其中包含多个潜在的代码流入口点。
总而言之,已经无数次注意到break 几乎始终是您希望在每个案例结束时拥有的默认行为。
【讨论】:
为什么编译器不自动在 switch 中的每个代码块后放置 break 语句?
抛开好希望能够在几种情况下使用相同的块(可能是特殊情况)......
是历史原因吗?您希望何时执行多个代码块?
它主要是为了与 C 兼容,可以说是古代goto 关键字在地球上漫游时的古老黑客。当然,它确实实现了一些令人惊奇的事情,例如Duff's Device,但无论是赞成还是反对,这都是……充其量是争论。
【讨论】:
Java 源自 C,其遗产包括一种称为 Duff's Device 的技术。
这是一种优化,它依赖于这样一个事实,即在没有break; 语句的情况下,控制权从一种情况转移到另一种情况。到 C 被标准化时,“在野外”有很多这样的代码,而改变语言来破坏这种结构会适得其反。
【讨论】:
我认为这是一个错误。作为一种语言结构,使用break 作为默认值同样容易,而使用fallthrough 关键字。我编写和阅读的大部分代码在每个案例之后都有一个中断。
【讨论】:
continue <case name>,它允许明确指定使用哪个case语句继续;
switch 中允许任意case 时,这将简单地变为goto。 ;-)
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);
}
}
【讨论】:
因为在某些情况下,您希望流过第一个块,例如避免在多个块中编写相同的代码,但仍然能够将它们划分为 mroe 控制。还有很多其他原因。
【讨论】:
没错,因为通过一些巧妙的布局,您可以级联执行块。
【讨论】:
因此,如果您需要多个案例来做同样的事情,您不必重复代码:
case THIS:
case THAT:
{
code;
break;
}
或者您可以执行以下操作:
case THIS:
{
do this;
}
case THAT:
{
do that;
}
以级联方式。
如果你问我,真的很容易出错/混淆。
【讨论】:
do this 和 do that ,但只运行 do that ?
有时将多个案例关联到同一个代码块会很有帮助,例如
case 'A':
case 'B':
case 'C':
doSomething();
break;
case 'D':
case 'E':
doSomethingElse();
break;
等等。只是一个例子。
根据我的经验,“失败”并为一个案例执行多个代码块通常是不好的风格,但在某些情况下可能会有用处。
【讨论】:
// Intentional fallthrough.。在我看来,这与其说是一种糟糕的风格,不如说是“容易忘记休息”。附言当然不是像答案本身那样简单。
case 以这种方式堆叠在一起,我不会理会评论。如果它们之间有代码,那么是的,评论可能是值得的。
case 中声明多个案例,如下所示:case 'A','B','C': doSomething(); case 'D','E': doSomethingElse();,而无需在案例之间休息。 Pascal 可以做到这一点:“case 语句将序数表达式的值与每个选择器进行比较,选择器可以是常量、子范围或以逗号分隔的列表。” (wiki.freepascal.org/Case)
编译器没有添加自动中断,因此可以通过从 1 和 2 中删除 break 语句来使用 switch/case 来测试 1 <= a <= 3 等条件。
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;
}
【讨论】: