哈希映射只是编译 switch 语句的一种方式,但在任何情况下,您都可以将重复的 cases 想象为试图在常规 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
所以你仍然有跳跃表,但你有两个。有点迂回的过程——取散列码,打开它,根据情况加载另一个常数,然后做一个“正常”的开关(我认为)。但我想是相同的基本过程?