Skip to content

Commit 0fa06f8

Browse files
committed
优化“为什么不能用string类型进行switch判断”
1 parent 05aceb8 commit 0fa06f8

File tree

2 files changed

+68
-15
lines changed

2 files changed

+68
-15
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ stackoverflow-Java-top-qa
3434
* [如何将String转换为enum](https://github.com/giantray/stackoverflow-java-top-qa/blob/master/contents/convert-a-string-to-an-enum-in-java.md)
3535
* [在Java中声明数组](https://github.com/giantray/stackoverflow-java-top-qa/blob/master/contents/Declare-array-in-Java.md)
3636
* [反射是什么及其用途](https://github.com/giantray/stackoverflow-java-top-qa/blob/master/contents/What-is-reflection-and-why-is-it-useful.md.md)
37-
37+
* [为什么不能用string类型进行switch判断](https://github.com/giantray/stackoverflow-java-top-qa/blob/master/contents/Why-can't-I-switch-on-a-String.md)
3838

3939
> 编程技巧
4040

contents/Why-can't-I-switch-on-a-String.md

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,81 @@
1-
# Why can't I switch on a String
1+
# 为什么不能用string类型进行switch判断
22

33
##问题描述
44
为什么不能用string类型进行switch判断?
5-
是否作为一个功能将会被添加如java后续版本中
6-
有人能给我一篇文章,或者解释一下为什么不能这样做,说明java中switch语句的运行方式
5+
在java的后续版本中,是否会增加这个新特性
6+
有人能给我一篇文章,解释一下为什么不能这样做,或者进一步说明java中switch语句的运行方式
77

88
##回答
9-
用string作为case来使用switch语句已经在java SE7中被实现了,距离这个问题被提出至少有16年了。一个明确的原因迟迟未提供,但是性能要求它必须这样做
9+
在switch语句中用string作为case,这个特性已经在java SE7 中被实现了,距离 [这个'bug'](http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=1223179) 被提出至少也有16年了。为何迟迟不提供这个特性,原因不明。但可以推测,可能跟性能有关
1010

1111
**Implementtation in JDK 7**
12-
这个特性现在已经被实现,`javac`with a ["de-sugaring peocess"](http://blogs.oracle.com/darcy/entry/project_coin_string_switch_anatomy)。一个高级的,清楚的使用String常量的语法能在case声明中被使用,会在编译时扩展成为更复杂的代码模式,生成的代码使用jvm指令也一直存在。
13-
switch将string用作case在编译时翻译成两个switch。第一次每一个string映射到一个独一无二的整数,它的位置在原始的switch上。这是通过在case上的hashcode进行switch选择,相应的案例是一个if语句,测试字符串是否相等。如果有哈希碰撞,测试就像`if-else-if`
14-
第二次switch反映在最初的源代码中,用相应位置上的整数代替响应的string标签case。这两步过程更容易保留原始switch的流程控制。
12+
13+
在JDK7中,这个特性已经实现了。在编译阶段,以string作为case值的代码,会按照特定的模式,被转换为更加复杂的代码。最终的执行代码将是一些使用了JVM指令的代码。
14+
15+
究竟是如何转换的呢?我们直接看看源码及编译后的代码。源代码:
16+
```
17+
public class StringInSwitchCase {
18+
public static void main(String[] args) {
19+
String mode = args[0];
20+
switch (mode) {
21+
case "ACTIVE":
22+
System.out.println("Application is running on Active mode");
23+
break;
24+
case "PASSIVE":
25+
System.out.println("Application is running on Passive mode");
26+
break;
27+
case "SAFE":
28+
System.out.println("Application is running on Safe mode");
29+
}
30+
}
31+
}
32+
```
33+
编译后再反编译的代码:
34+
```
35+
import java.io.PrintStream;
36+
37+
public class StringInSwitchCase{
38+
public StringInSwitchCase() { }
39+
40+
public static void main(string args[]) {
41+
String mode = args[0];
42+
String s; switch ((s = mode).hashCode()) {
43+
default: break;
44+
case -74056953:
45+
if (s.equals("PASSIVE")) {
46+
System.out.println("Application is running on Passive mode");
47+
}
48+
break;
49+
case 2537357:
50+
if (s.equals("SAFE")) {
51+
System.out.println("Application is running on Safe mode");
52+
}
53+
break;
54+
case 1925346054:
55+
if (s.equals("ACTIVE")) {
56+
System.out.println("Application is running on Active mode");
57+
}
58+
break;
59+
}
60+
}
61+
}
62+
```
63+
64+
包含case string的 switch 语句,在编译时会转为为嵌套代码(switch+if)。第一个switch将 case 中的string转为唯一的integer值。这个integer值就是原先string的hashcode值。在case的逻辑中,会加入if语句,这个if语句用于进一步检查string值是否跟原先的case string匹配。这样可以防止hash碰撞,确保代码的健壮。这本质上是一种语法糖,既支持了string作为case值这一特性,又能确保逻辑正确性。
1565

1666
**Switchs in the JVM**
17-
在switch的更多深层技术上,可以参考JVM规范,[compliation of switch statements](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.10)有响应的描述。简单概括说,根据使用的常量的多少,switch有两种不同的JVM指令来执行,每种情况下都取决于使用整数常量来有效的执行。
1867

19-
如果常量很多,case会在一个指令表被用作索引(减去最小值后)——`tablewitch`指令
20-
如果常量相对较少,那么可用二分查找来找到正确的case--`lookupswitch`指令
68+
switch的更多深层技术实现,可以参考JVM规范,[compliation of switch statements](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.10)。简单概括说,根据使用的常量的多寡,switch会对应到两种不同的JVM指令。JVM指令有所不同,归根结底都是为了代码的效率。
2169

22-
`de-sugaring`中,一个switch(String)的对象,两种指令都会被用到。`lookupswitch`指令适用于第一次计算哈希值找到初始位置,由此产生的顺序表很自然的用`tableswitch`指令
70+
如果常量很多,会将case的int值去掉最低位后作为索引,放到一个指针表中——也就是所谓的`tablewitch`指令
2371

24-
两种指令在编译时都需要将整数常量赋值给每个case。在运行时,虽然`tableswitch`O(1)的性能通常要好于`lookupswitch`O(log(n))的性能,但是前者需要一定的分析来决定是否有足够的case来满足时间空间的平衡。Bill Venners的文章[a great article](http://www.artima.com/underthehood/flowP.html)在细节上讲述的更多,以及涉及到从底层的出发,来看其他java流程控制指令。
72+
如果常量相对较少,那么可用二分查找来找到正确的case--也就是所谓的`lookupswitch`指令
73+
74+
这两种指令,都要求在编译时确保case的对应值是integer常量。在运行时,虽然`tableswitch`O(1)的性能通常要好于`lookupswitch`O(log(n))的性能。但是前者需要更多的空间开销,因此需要兼顾空间及时间综合考虑性价比。Bill Venners的文章[a great article](http://www.artima.com/underthehood/flowP.html)有更多深入的分析。
2575

2676
**Before JDK 7**
27-
在JDK之前,枚举近似的用String作为switch的case,这使用了静态方法`valueOf`,编译器生成了一个枚举类型。例子如下:
77+
78+
在JDK之前,可以用枚举来实现类似的需求。它和在case中使用string有异曲同工之妙。例如如下:
2879
```
2980
Pill p = Pill.valueOf(str);
3081
switch(p) {
@@ -33,7 +84,9 @@ switch(p) {
3384
}
3485
```
3586

36-
[阅读原文:Why can`t I switch on a String](http://stackoverflow.com/questions/338206/why-cant-i-switch-on-a-string)
87+
[stackoverflow原链接:Why can`t I switch on a String](http://stackoverflow.com/questions/338206/why-cant-i-switch-on-a-string)
88+
89+
[可参考中文文章《Java中字符串switch的实现细节》](http://www.deepinmind.com/java/2014/05/08/how-string-in-switch-works-in-java-7.html)
3790

3891

3992

0 commit comments

Comments
 (0)