case表达式既可以用字面值常量,也可以用final修饰且初始化过的变量。例如以下代码可正常编译并执行:
public static int test(int i) {
final int j = 2;
int result;
switch (i) {
case 0:
result = 0;
break;
case j:
result = 1;
break;
case 10:
result = 4;
break;
default:
result = -1;
}
return result;
}
但是没有初始化就不行,比如下面的代码就无法通过编译
public class SwitchTest {
private final int caseJ;
public int test(int i) {
int result;
switch (i) {
case 0:
result = 0;
break;
case caseJ:
result = 1;
break;
case 10:
result = 4;
break;
default:
result = -1;
}
return result;
}
SwitchTest(int caseJ) {
this.caseJ = caseJ;
}
public static void main(String[] args) {
SwitchTest testJ = new SwitchTest(1);
System.out.print(testJ.test(2));
}
}
下面两种几乎一样的代码,会编译出大相径庭的字节码。
public static int test(int i) {
int result;
switch (i) {
case 0:
result = 0;
break;
case 2:
result = 1;
break;
case 10:
result = 4;
break;
default:
result = -1;
}
return result;
}
对应字节码
public static int test(int);
Code:
0: iload_0
1: lookupswitch { // 3
0: 36
2: 41
10: 46
default: 51
}
36: iconst_0
37: istore_1
38: goto 53
41: iconst_1
42: istore_1
43: goto 53
46: iconst_4
47: istore_1
48: goto 53
51: iconst_m1
52: istore_1
53: iload_1
54: ireturn
public static int test(int i) {
int result;
switch (i) {
case 0:
result = 0;
break;
case 2:
result = 1;
break;
case 4:
result = 4;
break;
default:
result = -1;
}
return result;
}
public static int test(int);
Code:
0: iload_0
1: tableswitch { // 0 to 4
0: 36
1: 51
2: 41
3: 51
4: 46
default: 51
}
36: iconst_0
37: istore_1
38: goto 53
41: iconst_1
42: istore_1
43: goto 53
46: iconst_4
47: istore_1
48: goto 53
51: iconst_m1
52: istore_1
53: iload_1
54: ireturn
两种字节码,最大的区别是执行了不同的指令:lookupswitch和tableswitch。
但是,在分支比较少的情况下,O(log n)其实并不大。n=2时,log n 约为2.8;即使n=100, log n 约为 6.6,与1仍未达到1个数量级的差距。
在JDK1.8环境下,通过检索langtools
这个包,可以在langtools/src/share/classes/com/sun/tools/javac/jvm/Gen.java看到以下代码:
long table_space_cost = 4 + ((long) hi - lo + 1); // words
long table_time_cost = 3; // comparisons
long lookup_space_cost = 3 + 2 * (long) nlabels;
long lookup_time_cost = nlabels;
int opcode =
nlabels > 0 && table_space_cost + 3 * table_time_cost <= lookup_space_cost + 3 * lookup_time_cost
?
tableswitch : lookupswitch;
这段代码的上下文:
可以看出,决策的条件综合考虑了时间复杂度(table_time_cost/lookup_time_cost)和空间复杂度(table_space_cost/lookup_space_cost),并且时间复杂度的权重是空间复杂度的3倍。
存疑点:
一般来说,更多的限制能带来更好的性能。
从上文可以看出,无论是tableswitch还是lookupswitch,都有对随机查找的优化,而if...else...是没有的,可以看下面的源码和字节码。
public static int test2(int i) {
int result;
if(i == 0) {
result = 0;
} else if(i == 1) {
result = 1;
} else if(i == 4) {
result = 4;
} else {
result = -1;
}
return result;
}
public static int test2(int);
Code:
0: iload_0
1: ifne 9
4: iconst_0
5: istore_1
6: goto 31
9: iload_0
10: iconst_1
11: if_icmpne 19
14: iconst_1
15: istore_1
16: goto 31
19: iload_0
20: iconst_4
21: if_icmpne 29
24: iconst_4
25: istore_1
26: goto 31
29: iconst_m1
30: istore_1
31: iload_1
32: ireturn
举例如下,这段源码有两个特点:
public static int testString(String str) {
int result = -4;
switch (str) {
case "abc":
result = 0;
break;
case "def":
result = 1;
break;
case "ghi":
break;
case "test":
case "test2":
result = 1;
break;
default:
result = -1;
}
return result;
}
对应字节码
public static int testString(java.lang.String);
Code:
0: bipush -4
2: istore_1
3: aload_0
4: astore_2
5: iconst_m1
6: istore_3
7: aload_2
8: invokevirtual #2 // Method java/lang/String.hashCode:()I
11: lookupswitch { // 5
96354: 60
99333: 74
102312: 88
3556498: 102
110251488: 116
default: 127
}
60: aload_2
61: ldc #3 // String abc
63: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
66: ifeq 127
69: iconst_0
70: istore_3
71: goto 127
74: aload_2
75: ldc #5 // String def
77: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
80: ifeq 127
83: iconst_1
84: istore_3
85: goto 127
88: aload_2
89: ldc #6 // String ghi
91: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
94: ifeq 127
97: iconst_2
98: istore_3
99: goto 127
102: aload_2
103: ldc #7 // String test
105: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
108: ifeq 127
111: iconst_3
112: istore_3
113: goto 127
116: aload_2
117: ldc #8 // String test2
119: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
122: ifeq 127
125: iconst_4
126: istore_3
127: iload_3
128: tableswitch { // 0 to 4
0: 164
1: 169
2: 174
3: 177
4: 177
default: 182
}
164: iconst_0
165: istore_1
166: goto 184
169: iconst_1
170: istore_1
171: goto 184
174: goto 184
177: iconst_1
178: istore_1
179: goto 184
182: iconst_m1
183: istore_1
184: iload_1
185: ireturn
可以看到与整型常量的不同:
为什么要再生成一段tableswitch?从字节码来看,两个平行的分支("test"和"test2"),虽然没有在tableswitch中用同一个数组下标,但是使用了同一个跳转行177,在这种情况下减少了字节码冗余。
样例代码如下
public static int testEnum(StatusEnum statusEnum) {
int result;
switch (statusEnum) {
case INIT:
result = 0;
break;
case FINISH:
result = 1;
break;
default:
result = -1;
}
return result;
}
对应字节码
public static int testEnum(com.example.StatusEnum);
Code:
0: getstatic #9 // Field com/example/SwitchTest$1.$SwitchMap$com$example$core$service$domain$enums$StatusEnum:[I
3: aload_0
4: invokevirtual #10 // Method com/example/core/service/domain/enums/StatusEnum.ordinal:()I
7: iaload
8: lookupswitch { // 2
1: 36
2: 41
default: 46
}
36: iconst_0
37: istore_1
38: goto 48
41: iconst_1
42: istore_1
43: goto 48
46: iconst_m1
47: istore_1
48: iload_1
49: ireturn
可以看到,使用了枚举的ordinal方法确定序号。
通过查看字节码,可以发现源码的break关键字,对应的是字节码goto到具体行的语句。 如果不用break,那么对应的字节码就会“滑落”到下一行语句,继续执行。
上一篇:人工智能还能治病?
¥498.00
¥399.00
¥29.00
¥299.00