为啥 Java 中的 switchcase 语句是这样工作的? [复制]

Posted

技术标签:

【中文标题】为啥 Java 中的 switchcase 语句是这样工作的? [复制]【英文标题】:Why the switchcase statement in Java works like this? [duplicate]为什么 Java 中的 switchcase 语句是这样工作的? [复制] 【发布时间】:2013-06-03 07:13:29 【问题描述】:

为什么 Java 中的 switch case 语句只接受整数、短字节、字节和字符,而不接受其他数据类型?有什么好处?请详细说明。

【问题讨论】:

自 JDK 1.7 起它也接受字符串和枚举。 这是语言要求。在 Java 7 下,它现在支持String,并且由于引入了enum,它还支持enum。问题实际上归结为您将如何定义任意对象的案例? @MadProgrammer 好吧,您可以创建一个任意对象并使用 equals 来匹配我猜的每个案例。 @Thihara 但是toString 呢?我认为问题是你想匹配一个对象与另一个对象的相等性还是对象的属性? @EdwinDalorzo 开关接受枚举,因为添加了枚举(Java 5,2004 年) 【参考方案1】:

通常,语言设计问题归结为“因为这是设计师决定这样做的方式”。这只是另一个时代。

但是Java有一些起源于C,它做同样的事情,并且在80年代这个决定被解释为因为编译器可以将开关变成一个跳转表:基本上,每个代码块的地址都是放入表中,switch 成为范围检查,然后使用您传入的值进行表查找(通常索引到数组或至少是数组的链表)以获取地址,然后跳转到该地址。在这种情况下,只有整数才有意义。请记住,计算机并不总是像现在这样快。 C 是在 70 年代初基于 60 年代后期的工作设计的,当时计算机速度要慢得多。

与 Java 和 C 具有相同句法传统的某些语言(例如 javascript)使 switch 只是编写 if...else/if...else 的另一种方式,并且不将检查的类型限制为整数类型,也许是因为,在 90 年代设计,这成为一个现实的选择。或者可能只是因为 JavaScript 的设计者 (Brendan Eich) 更喜欢这种方式。


下面,Baadshah 提问:

出于好奇:那么现在它如何支持字符串?可以给点意见吗?

首先,让我们回过头来看看int的案例:

  num = Integer.parseInt(args[0]);
  switch (num) 
      case 1:
          System.out.println("You used the special value one");
          break;
      case 42:
          System.out.println("You used the special value forty-two");
          break;
      case 67:
          System.out.println("You used the special value sixty-seven");
          break;
      default:
          System.out.println("You used the a non-special value " + num);
          break;
  

生成这样的字节码:

 19: iload_2
 20: 查找开关  // 3
                1:56
               42:67
               67:78
          默认值:89
     
 56: getstatic #8 // 字段 java/lang/System.out:Ljava/io/PrintStream;
 59: ldc #9 // String 你使用了特殊值 one
 61: invokevirtual #10 // 方法 java/io/PrintStream.println:(Ljava/lang/String;)V
 64:转到 114
 67: getstatic #8 // 字段 java/lang/System.out:Ljava/io/PrintStream;
 70: ldc #11 // String 你使用了特殊值 42
 72: invokevirtual #10 // 方法 java/io/PrintStream.println:(Ljava/lang/String;)V
 75:转到 114
 78: getstatic #8 // 字段 java/lang/System.out:Ljava/io/PrintStream;
 81: ldc #12 // String 你使用了特殊值 67
 83: invokevirtual #10 // 方法 java/io/PrintStream.println:(Ljava/lang/String;)V
 86:转到 114
 89: getstatic #8 // 字段 java/lang/System.out:Ljava/io/PrintStream;
 92:新 #13 // 类 java/lang/StringBuilder
 95:重复
 96: invokespecial #14 // 方法 java/lang/StringBuilder."":()V
 99: ldc #15 // String 你使用了非特殊值
101: invokevirtual #16 // 方法 java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
104:iload_2
105: invokevirtual #17 // 方法 java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
108: invokevirtual #18 // 方法 java/lang/StringBuilder.toString:()Ljava/lang/String;
111: invokevirtual #10 // 方法 java/io/PrintStream.println:(Ljava/lang/String;)V

我们可以在int 上看到表格查找的实际效果。

那么你如何用字符串做到这一点?嗯,一个答案就是将switch 变成if...else if...else 结构。但他们做了比这更聪明的事情:他们使用哈希码进行优化,然后使用equals 来防止冲突:

  switch (str) 
      case "abc":
          System.out.println("You used the special value 'abc'");
          break;
      case "def":
          System.out.println("You used the special value 'def'");
          break;
      case "ghi":
          System.out.println("You used the special value 'ghi'");
          break;
      default:
          System.out.println("You used the a non-special value '" + str + "'");
          break;
  

变成:

124: 加载 4
126: invokevirtual #19 // 方法 java/lang/String.hashCode:()I
129: 查找开关  // 3
            96354:164
            99333:180
           102312:196
          默认:209
     
164:加载 4
166: ldc #20 // 字符串 abc
168: invokevirtual #21 // 方法 java/lang/String.equals:(Ljava/lang/Object;)Z
171:ifeq 209
174:图标st_0
175:istore 5
177:转到209
180:加载 4
182: ldc #22 // 字符串定义
184: invokevirtual #21 // 方法 java/lang/String.equals:(Ljava/lang/Object;)Z
187:ifeq 209
190:图标st_1
191:istore 5
193:转到209
196:加载 4
198: ldc #23 // 字符串 ghi
200: invokevirtual #21 // 方法 java/lang/String.equals:(Ljava/lang/Object;)Z
203:ifeq 209
206:图标st_2
207:istore 5
209:加载 5
211: tableswitch  // 0 到 2
                0:236
                1:247
                2:258
          默认值:269
     
236: getstatic #8 // 字段 java/lang/System.out:Ljava/io/PrintStream;
239: ldc #24 // String 你使用了特殊值'abc'
241: invokevirtual #10 // 方法 java/io/PrintStream.println:(Ljava/lang/String;)V
244:转到299
247: getstatic #8 // 字段 java/lang/System.out:Ljava/io/PrintStream;
250: ldc #25 // String 你使用了特殊值'def'
252: invokevirtual #10 // 方法 java/io/PrintStream.println:(Ljava/lang/String;)V
255:转到299
258: getstatic #8 // 字段 java/lang/System.out:Ljava/io/PrintStream;
261: ldc #26 // String 你使用了特殊值'ghi'
263: invokevirtual #10 // 方法 java/io/PrintStream.println:(Ljava/lang/String;)V
266:转到299
269: getstatic #8 // 字段 java/lang/System.out:Ljava/io/PrintStream;
272:新的 #13 // 类 java/lang/StringBuilder
275:重复
276: invokespecial #14 // 方法 java/lang/StringBuilder."":()V
279: ldc #27 // String You used the a non-special value '
281: invokevirtual #16 // 方法 java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
284:加载_3
285: invokevirtual #16 // 方法 java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
288: ldc #28 // 字符串'
290: invokevirtual #16 // 方法 java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
293:invokevirtual #18 // 方法 java/lang/StringBuilder.toString:()Ljava/lang/String;
296: invokevirtual #10 // 方法 java/io/PrintStream.println:(Ljava/lang/String;)V

看看他们在那里做了什么?现在基本上是两个switches:一个根据哈希码为每个案例获取一个唯一编号(但使用equals 进行双重检查),然后第二个发送。

【讨论】:

出于好奇:那么现在它如何支持字符串?你能给出一些想法吗? @Baadshah:一个非常好的问题!我已经更新以解决它。 感谢 Crowder.Awsome 对简单评论的解释。【参考方案2】:

JDK6 switch 语句适用于 char、byte、int 原始数据类型和枚举。在 JDK 7 中,他们意识到 java.lang.String 也是一个常量,并已添加到 switch 语句支持的数据类型列表中。

例如下面的代码在 JDK7 中运行良好。

 public static void OpenSource(String language) 

 switch (language) 
 case "PERL":
   System.out.println("PERL");
   break;
 case "Python":
   System.out.println("Python");
   break;
 case "Ruby":
   System.out.println("Ruby");
   break;
 case "php":
   System.out.println("PHP");
   break;
  default:
   throw new IllegalArgumentException();
 

【讨论】:

以上是关于为啥 Java 中的 switchcase 语句是这样工作的? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

在 switch case 语句中,它说“重复的 case 值”作为错误出现。为啥?

Java switch case和数组

Java中的结构语句

Java switch case 语句

switch case 支持的 6 种数据类型!

switch语句小练习