问题
I read JVM specification on compiling switches and became interested in how switch statement on String is compiled. Here is the test method I examined (JDK1.7.0_40):
static int test(String i) {
switch (i) {
case "a": return -100;
case "45b": return 1;
case "c": return 2;
default: return -1;
}
}
I expect this method to be compiled into simple lookupswitch on hashCode of string, but suddenly
static int test(java.lang.String);
Code:
0: aload_0
1: astore_1
2: iconst_m1
3: istore_2
4: aload_1
5: invokevirtual #6 // Method java/lang/String.hashCode:()I
8: lookupswitch { // 3
97: 44
99: 72
51713: 58
default: 83
}
44: aload_1
45: ldc #7 // String a
47: invokevirtual #8 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
50: ifeq 83
53: iconst_0
54: istore_2
55: goto 83
58: aload_1
59: ldc #9 // String 45b
61: invokevirtual #8 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
64: ifeq 83
67: iconst_1
68: istore_2
69: goto 83
72: aload_1
73: ldc #10 // String c
75: invokevirtual #8 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
78: ifeq 83
81: iconst_2
82: istore_2
83: iload_2
84: tableswitch { // 0 to 2
0: 112
1: 115
2: 117
default: 119
}
112: bipush -100
114: ireturn
115: iconst_1
116: ireturn
117: iconst_2
118: ireturn
119: iconst_m1
120: ireturn
As you could see, in branches of first lookupswitch JVM is not doing the real work generating indices for subsequent tableswitch (line 84) instead.
Tableswitch should work fast so doesn't bring a lot of extra work. But anyway, what is the purpose of generating additional switch?
Update
I understand possibility of hashCode collisions. What I'm trying to say is that instead of subsequent tableswitch, compiler could move all the real work from subsequent tableswitch to first and then use ifeq to jump to the end of all switch branches. So the one possible answer I see here: that in first switch compiler tries to precompute label for ifeq jump based on known number of cases, but I'm not sure that it is the only reason.
Update2
As @ericbn suggested, I tried to compile
switch (i) {
case 97: return -100;
case 51713: return 1;
case 99: return 2;
default: return -1;
}
with i as int and compiler gave me plain lookupswitch.
回答1:
The cite from the javac source code:
* The general approach used is to translate a single
* string switch statement into a series of two chained
* switch statements: the first a synthesized statement
* switching on the argument string's hash value and
* computing a string's position in the list of original
* case labels, if any, followed by a second switch on the
* computed integer value. The second switch has the same
* code structure as the original string switch statement
* except that the string case labels are replaced with
* positional integer constants starting at 0.
*
* The first switch statement can be thought of as an
* inlined map from strings to their position in the case
* label list. An alternate implementation would use an
* actual Map for this purpose, as done for enum switches.
*
* With some additional effort, it would be possible to
* use a single switch statement on the hash code of the
* argument, but care would need to be taken to preserve
* the proper control flow in the presence of hash
* collisions and other complications, such as
* fallthroughs. Switch statements with one or two
* alternatives could also be specially translated into
* if-then statements to omit the computation of the hash
* code.
来源:https://stackoverflow.com/questions/25568639/why-switch-on-string-compiles-into-two-switches