【问题标题】:Why do we need break after case statements?为什么我们在 case 语句之后需要 break?
【发布时间】:2011-02-12 05:02:18
【问题描述】:

为什么编译器不自动在 switch 中的每个代码块之后放置 break 语句?是因为历史原因吗?您希望何时执行多个代码块?

【问题讨论】:

  • Made an answer 关于 JDK-12 和开关标签已改组为不强制要求 break

标签: java switch-statement case language-design break


【解决方案1】:

在 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

【讨论】:

    【解决方案2】:

    我现在正在处理我在 switch 语句中需要 break 的项目,否则代码将无法工作。请耐心等待,我会给你一个很好的例子,说明为什么在你的 switch 语句中需要break

    假设您有三种状态,一种等待用户输入数字,第二种等待计算,第三种打印总和。

    在这种情况下,你有:

    1. State1 - 等待用户输入数字
    2. State2 - 打印总和
    3. state3 - 计算总和

    查看状态,您可能希望从 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,它将按照state1state2state3的顺序执行。但是使用break,我们可以避免这种情况,并且可以在正确的过程中进行排序,即从state1开始,然后是state3,最后是state2。

    【讨论】:

      【解决方案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
      

      正如预期的那样。

      【讨论】:

        【解决方案4】:

        您可以轻松区分其他类型的数字、月份、计数。
        如果在这种情况下,这会更好;

        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;
             }
         }
        

        【讨论】:

          【解决方案5】:

          这是一个老问题,但实际上我今天遇到了使用 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。

          【讨论】:

            【解决方案6】:

            你可以做各种有趣的事情。

            例如,假设您想针对所有情况执行特定操作,但在某些情况下您想要执行该操作以及其他操作。使用带有 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 语句时,好的编译器会警告你。

            【讨论】:

            • Java 中的字符串可以使用 switch/case 吗?
            • @Steve:哎呀,我想目前还没有。根据stackoverflow.com/questions/338206/…,未来版本的Java 将允许使用字符串。 (我目前的大部分编程都是在 C# 中进行的,它确实允许在 switch 语句中使用字符串。)我已经编辑了答案以删除误导性的引号。
            • @ZachJohnson,很久以后,Java 7 确实允许打开字符串。
            【解决方案7】:

            就历史记录而言,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,而无暇顾及这样的细节 :-)

            【讨论】:

            • 这应该会得到更多的选票。显然,OP 已经知道省略 break 允许“执行多个代码块”,并且更关心这种设计选择的动机。其他人提到了从 C 到 Java 的著名遗产,这个答案将研究进一步推到了 C 之前的时代。我希望我们从一开始就有这种(虽然非常原始)模式匹配。
            【解决方案8】:

            Historically,这是因为case 本质上定义了label,也称为target pointgoto 调用。 switch 语句及其相关案例实际上只是代表了一个多路分支,其中包含多个潜在的代码流入口点。

            总而言之,已经无数次注意到break 几乎始终是您希望在每个案例结束时拥有的默认行为。

            【讨论】:

              【解决方案9】:

              为什么编译器不自动在 switch 中的每个代码块后放置 break 语句?

              抛开希望能够在几种情况下使用相同的块(可能是特殊情况)......

              是历史原因吗?您希望何时执行多个代码块?

              它主要是为了与 C 兼容,可以说是古代goto 关键字在地球上漫游时的古老黑客。当然,它确实实现了一些令人惊奇的事情,例如Duff's Device,但无论是赞成还是反对,这都是……充其量是争论。

              【讨论】:

                【解决方案10】:

                Java 源自 C,其遗产包括一种称为 Duff's Device 的技术。 这是一种优化,它依赖于这样一个事实,即在没有break; 语句的情况下,控制权从一种情况转移到另一种情况。到 C 被标准化时,“在野外”有很多这样的代码,而改变语言来破坏这种结构会适得其反。

                【讨论】:

                  【解决方案11】:

                  我认为这是一个错误。作为一种语言结构,使用break 作为默认值同样容易,而使用fallthrough 关键字。我编写和阅读的大部分代码在每个案例之后都有一个中断。

                  【讨论】:

                  • 我宁愿建议continue <case name>,它允许明确指定使用哪个case语句继续;
                  • @Vilx 当在当前switch 中允许任意case 时,这将简单地变为goto。 ;-)
                  【解决方案12】:

                  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);
                      }
                  }
                  

                  【讨论】:

                  • 有人瞄准了向上的箭头却错过了?或者,也许他们对你的大括号样式或缩进不满意......
                  • 不知道,所以请给我一个 +1。这是一个失败的例子,尽管我真的希望 Java 选择了一个更现代的 case 语句。 Cobol 的 EVALUATE-WHEN-OTHERWISE 功能要强大得多,并且早于 Java。 Scala 的匹配表达式是一个现代示例。
                  • 我的学生会因为这样做而被公开鞭打。土狼很丑。
                  • @ncmathsadist 它说明了一种做事方式的观点。我不同意这个例子可能是极端的。但这是一个真实的例子,我相信它可以帮助人们理解一个概念。
                  【解决方案13】:

                  因为在某些情况下,您希望流过第一个块,例如避免在多个块中编写相同的代码,但仍然能够将它们划分为 mroe 控制。还有很多其他原因。

                  【讨论】:

                    【解决方案14】:

                    没错,因为通过一些巧妙的布局,您可以级联执行块。

                    【讨论】:

                      【解决方案15】:

                      因此,如果您需要多个案例来做同样的事情,您不必重复代码:

                      case THIS:
                      case THAT:
                      {
                          code;
                          break;
                      }
                      

                      或者您可以执行以下操作:

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

                      以级联方式。

                      如果你问我,真的很容易出错/混淆。

                      【讨论】:

                      • 是否同时运行 do thisdo that ,但只运行 do that
                      • 只需阅读文档。那是糟糕的!编写错误的简单方法!
                      【解决方案16】:

                      有时将多个案例关联到同一个代码块会很有帮助,例如

                      case 'A':
                      case 'B':
                      case 'C':
                          doSomething();
                          break;
                      
                      case 'D':
                      case 'E':
                          doSomethingElse();
                          break;
                      

                      等等。只是一个例子。

                      根据我的经验,“失败”并为一个案例执行多个代码块通常是不好的风格,但在某些情况下可能会有用处。

                      【讨论】:

                      • 当您省略休息时,请始终添加注释 // Intentional fallthrough.。在我看来,这与其说是一种糟糕的风格,不如说是“容易忘记休息”。附言当然不是像答案本身那样简单。
                      • @doublep - 我同意。在我看来,如果可能的话,我会避免它,但如果它有意义,那么请确保你在做什么非常清楚。
                      • @doublep:如果多个 case 以这种方式堆叠在一起,我不会理会评论。如果它们之间有代码,那么是的,评论可能是值得的。
                      • 我想象一种语言,您可以在一个 case 中声明多个案例,如下所示:case 'A','B','C': doSomething(); case 'D','E': doSomethingElse();,而无需在案例之间休息。 Pascal 可以做到这一点:“case 语句将序数表达式的值与每个选择器进行比较,选择器可以是常量、子范围或以逗号分隔的列表。” (wiki.freepascal.org/Case)
                      【解决方案17】:

                      编译器没有添加自动中断,因此可以通过从 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;
                      }
                      

                      【讨论】:

                      • 耶奇。我完全讨厌这个。
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2018-02-26
                      • 1970-01-01
                      • 1970-01-01
                      • 2017-10-25
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多