为什么在case语句后面需要加上break关键字?

105

编译器为什么不会在每个代码块结束后自动添加break语句?这是出于历史原因吗?何时需要执行多个代码块?


2
发布了一个关于JDK-12和switch标签改革的回答,不再强制使用break - Naman
16个回答

101

有时候将多个情况与相同的代码块关联起来是很有帮助的,比如

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

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

等等,只是一个例子。

在我的经验中,通常情况下,在一个case下执行多个代码块不是好的编程风格,但在某些情况下可能会有用处。


32
当你省略一个break语句时,只需添加注释“// Intentional fallthrough.”。在我看来,这不是一种糟糕的风格,而是“容易偶然忘记break”的情况。附注:当然,在像答案本身这样简单的情况下不需要添加该注释。 - user319799
@doublep - 我同意。在我看来,如果可能的话,我会避免使用它,但如果有意义的话,请确保非常清楚你正在做什么。 - WildCrustacean
6
如果多个 case 被堆叠在一起,我就不会费心写注释。如果它们之间有代码,则可能需要注释。 - Billy ONeal
4
我想到一种语言,你可以在一个case语句中声明多个情况,像这样: case 'A','B','C':doSomething(); case 'D','E':doSomethingElse(); ,不需要在情况之间加上break。Pascal可以做到这一点:“case语句将枚举表达式的值与每个选择器进行比较,选择器可以是常量、子范围或由逗号分隔的列表。”(http://wiki.freepascal.org/Case) - Christian Semrau
可怕的Java语法 - frhack

34

历史上,这是因为case本质上定义了一个label,也就是跳转语句的目标点。Switch语句及其相关的各种case实际上代表了一种多路分支,可以有多个潜在的入口点进入一段代码流。

尽管如此,几乎无数次地注意到,在每个case结束时,break几乎总是你希望拥有的默认行为。


32

Java的语法来源于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);
    }
}

5
有人瞄准了向上的箭头但没有成功?或许他们对你的代码风格或缩进有意见… - Jim Lewis
不知道,所以我给你点赞。这是一个例子,其中的 fall-through 有所帮助,尽管我真的希望 Java 选择了更现代的 case 语句。Cobol 的 EVALUATE-WHEN-OTHERWISE 更加强大,并且比 Java 更早。Scala 的 match 表达式是一个现代化的例子。 - Jim Ferrans
2
我的学生如果做这件事会被公开鞭打。这太糟糕了。 - ncmathsadist
2
@ncmathsadist它说明了一种执行某个操作的方法。我并不认为这个例子是非常极端的,但它确实是一个真实世界的例子,我认为这有助于人们理解一个概念。 - Romain Hippeau

14

你可以通过case穿透实现各种有趣的事情。

例如,假设你想对所有情况执行特定操作,但在某个情况下,你还想执行该操作和其他一些操作。使用带有case穿透的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语句时发出警告。


在Java中,能否使用switch/case处理字符串? - Steve Kuo
@Steve:糟糕,我想现在还不行。根据https://dev59.com/8XRC5IYBdhLWcg3wUfN2#338230的说法,字符串将在Java的未来版本中被允许在switch语句中使用。(我目前大部分编程都是在C#中进行的,它允许在switch语句中使用字符串。)我已经编辑了答案以删除误导性的引号。 - Zach Johnson
2
@ZachJohnson,Java 7 版本之后,确实允许对字符串进行 switch 操作。 - Bob Cross

8
在 switch 语句中,case 后面的 break 用于避免 switch 语句中的 fallthrough。有趣的是,现在可以通过新形成的 switch 标签来实现这一点,该标签是通过 JEP-325 实现的。
通过这些更改,可以避免每个 switch case 中的 break,如下所示:
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

7
为什么编译器不会在switch中的每个代码块后自动添加break语句? 除了希望能够在多个情况下使用相同的代码块(这可能需要特殊处理)之外... 是出于历史原因吗?何时需要执行多个代码块? 主要是为了与C兼容,可以说是旧时代“goto”关键字存在的一个古老技巧。当然,它确实可以实现一些惊人的事情,比如Duff's Device,但是否支持该技巧还是有争议的。

4
因此,如果您需要多个案例执行相同的操作,就不必重复编写代码:
case THIS:
case THAT:
{
    code;
    break;
}

或者你可以做这样的事情:

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

以级联的方式。

如果你问我,这确实容易出现错误和混淆。


这个会同时运行 do thisdo that 吗?还是只有在这种情况下才会运行 do that - JonnyRaa
1
只需要阅读文档就可以了。这太糟糕了!这是写出错误的简单方法! - JonnyRaa

4
就历史记录而言,托尼·霍尔在20世纪60年代“结构化编程”革命期间发明了case语句。 托尼的case语句支持每个case多个标签和自动退出,无需使用烦人的break语句。需要显式的break是BCPL / B / C系列中出现的内容。丹尼斯·里奇(Dennis Ritchie)在ACM HOPL-II中写道:
例如,在我们在20世纪60年代学习BCPL时,并没有像BCPL switchon语句那样的endcase存在,因此从B和C switch语句中重载break关键字以逃脱是由于分歧演变而不是有意识的更改。
我没有找到任何关于BCPL的历史文献,但里奇的评论表明break或多或少是历史偶然事件。 BCPL后来解决了这个问题,但也许里奇和汤普森太忙于发明Unix而没时间去烦这样的细节 :-)

这条回复应该得到更多的投票。显然,原帖作者已经知道省略 break 会允许“多个代码块执行”,更关注的是这种设计选择的动机。其他人提到了从 C 到 Java 的众所周知的遗产,而这个答案甚至将研究推到了 C 语言之前的年代。我希望我们从一开始就有这种(虽然非常原始)的模式匹配。 - wlnirvana

3

正如之前所说的一样,这是为了允许贯穿,不是错误,而是一种特性。 如果太多的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

正如预期的。


3

Java源自C语言,其继承了一种称为Duff's Device的技术。这是一种优化技术,它依赖于在没有break;语句的情况下从一个case转到下一个case。在C语言标准化之前,已经有很多这样的代码存在,如果改变语言以打破这些构造,将会适得其反。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接