我知道这将无法编译:
int caseNum = 2;
switch(caseNum)
{
case 2:
System.out.println("Happy");
break;
case 2:
System.out.println("Birthday");
break;
case 2:
System.out.println("To the ground!");
break;
default:
System.out.println("<3");
break;
}
我知道 case 语句是冲突的,并且编译器“不知道我在谈论哪个‘case 2’”。 我和我自己的一些同行想知道幕后的冲突是什么,并且听说 switch 语句被转换为哈希映射。 是这样吗,switch 语句是否在编译时变成哈希映射,并且映射中的冲突会导致错误?
到目前为止,我已经在 Stack Overflow 和 Google 上寻找答案,并且信息肯定已经存在,但我不确定如何正确表达它会出现的问题。 预先感谢!
哈希映射只是编译 switch 语句的一种方式,但在任何情况下,您都可以将重复的
case
想象为尝试在常规 HashMap
中为同一键拥有多个值。编译器不知道哪个值对应于该键,因此会发出错误。
switch
语句也可以编译成跳转表,在这种情况下,由于非常相似的原因,这仍然是不明确的——对于同一个跳转位置,您有多种不同的可能性。
switch
语句也可以编译成二分搜索,在这种情况下你仍然遇到同样的问题——搜索相同的键有多个不同的结果。
以防万一您好奇,我做了一个小测试用例来看看
javac
会将 switch
编译成什么。来自此(稍作修改)来源:
public static void main(final String[] args) {
final int caseNum = 2;
switch (caseNum) {
case 1:
System.out.println("Happy");
break;
case 2:
System.out.println("Birthday");
break;
case 3:
System.out.println("To the ground!");
break;
default:
System.out.println("<3");
break;
}
}
你得到这个字节码:
public static void main(java.lang.String[]);
Code:
0: iconst_2
1: tableswitch { // 1 to 3
1: 28
2: 39
3: 50
default: 61
}
28: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
31: ldc #3 // String Happy
33: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
36: goto 69
39: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
42: ldc #5 // String Birthday
44: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
47: goto 69
50: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
53: ldc #6 // String To the ground!
55: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
58: goto 69
61: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
64: ldc #7 // String <3
66: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
69: return
所以至少对于这个小
switch
来说,似乎使用了跳转表。我听说开关的编译方式部分取决于它们的大小,但我不知道实现发生变化的确切点。 20个案例好像还是用跳转表来实现的...
事实证明
String
switch 语句的实现方式有点不同。来自此来源:
public static void main(final String[] args) {
final String caseNum = "2";
switch (caseNum) {
case "1":
System.out.println("Happy");
break;
case "2":
System.out.println("Birthday");
break;
case "3":
System.out.println("To the ground!");
break;
default:
System.out.println("<3");
break;
}
}
你得到这个字节码:
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String 2
2: astore_2
3: iconst_m1
4: istore_3
5: aload_2
6: invokevirtual #3 // Method java/lang/String.hashCode:()I
9: tableswitch { // 49 to 51
49: 36
50: 50
51: 64
default: 75
}
36: aload_2
37: ldc #4 // String 1
39: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
42: ifeq 75
45: iconst_0
46: istore_3
47: goto 75
50: aload_2
51: ldc #2 // String 2
53: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
56: ifeq 75
59: iconst_1
60: istore_3
61: goto 75
64: aload_2
65: ldc #6 // String 3
67: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
70: ifeq 75
73: iconst_2
74: istore_3
75: iload_3
76: tableswitch { // 0 to 2
0: 104
1: 115
2: 126
default: 137
}
104: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
107: ldc #8 // String Happy
109: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
112: goto 145
115: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
118: ldc #10 // String Birthday
120: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
123: goto 145
126: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
129: ldc #11 // String To the ground!
131: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
134: goto 145
137: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
140: ldc #12 // String <3
142: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
145: return
所以你仍然有跳转表,但你有两个。有点迂回的过程——获取哈希码,根据情况加载另一个常量,然后进行“正常”切换(我认为)。但我想基本过程是一样的?
从历史上看,
switch
语句曾经/可以被实现为跳转表(即值到目标地址的映射)。根据表的实现,可能不可能有两个指向不同地址的相同值的条目。即使有可能,您将如何处理该重复值?从第一个处理程序返回,然后转到第二个处理程序?从不执行第二个处理程序?
允许这样做是没有意义的。