jdk1.7 String switch的实现

本文详细解析了JDK1.7中如何通过现有的tableswitch和lookupswitch指令来实现字符串Switch的功能,包括利用hashCode进行初步筛选,再通过equals方法确保准确性。

对于int的switch,jvm是用tableswitch和lookupswitch来实现的,jdk1.7 switch增加了对string的支持,那么底层是如何实现的呢?是否增加了新的指令或是否给某些指令增加了新的含义?

看这样一个程序:

Java代码 收藏代码
  1. publicclassTest{
  2. publicstaticvoidmain(String[]args){
  3. Stringname="b";
  4. intvalue=0;
  5. switch(name){
  6. case"a":
  7. value=1;
  8. break;
  9. case"b":
  10. value=2;
  11. break;
  12. case"c":
  13. value=3;
  14. break;
  15. case"d":
  16. value=4;
  17. break;
  18. case"e":
  19. value=5;
  20. break;
  21. default:
  22. value=6;
  23. }
  24. System.out.println(value);
  25. }
  26. }

javap -c Test得出的结果为:

Java代码 收藏代码
  1. publicstaticvoidmain(java.lang.String[]);
  2. Code:
  3. 0:ldc#2//Stringb
  4. 2:astore_1//将"b"赋值给name
  5. 3:iconst_0//将0入栈
  6. 4:istore_2//将0赋值给value
  7. 5:aload_1//将name(即"b")入栈
  8. 6:astore_3//将name(即"b")赋值给一个编译器生成的变量,记为tmpName(tmpName此时也是"b")
  9. 7:iconst_m1//将-1入栈
  10. 8:istore4//将-1赋值给一个编译器生成的变量,记为m1
  11. 10:aload_3//将tmpName(即"b")入栈
  12. 11:invokevirtual#3//Methodjava/lang/String.hashCode:()I调用tmpName的hashCode方法("b".hashCode(),得结果98)
  13. 14:tableswitch{//97to101//根据hashCode的值到不同的分支
  14. 97:48
  15. 98:63//这里走到这个分支,跳转到63
  16. 99:78
  17. 100:93
  18. 101:108
  19. default:120
  20. }
  21. 48:aload_3
  22. 49:ldc#4//Stringa
  23. 51:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)Z
  24. 54:ifeq120
  25. 57:iconst_0
  26. 58:istore4
  27. 60:goto120
  28. 63:aload_3//从14跳转到了这里,将tmpName(即"b")入栈
  29. 64:ldc#2//Stringb将"b"入栈
  30. 66:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)Z
  31. //比较tmpName和"b",如果相等,将1入栈,不等将0入栈.这里是相等的,入栈的为1
  32. 69:ifeq120//从栈顶取出比较结果,如果等于0,就跳到120,如果不等于0就继续下面的指令,这里显然不等于0
  33. 72:iconst_1//将1入栈
  34. 73:istore4//将1存储到m1中
  35. 75:goto120//跳到120
  36. 78:aload_3
  37. 79:ldc#6//Stringc
  38. 81:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)Z
  39. 84:ifeq120
  40. 87:iconst_2
  41. 88:istore4
  42. 90:goto120
  43. 93:aload_3
  44. 94:ldc#7//Stringd
  45. 96:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)Z
  46. 99:ifeq120
  47. 102:iconst_3
  48. 103:istore4
  49. 105:goto120
  50. 108:aload_3
  51. 109:ldc#8//Stringe
  52. 111:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)Z
  53. 114:ifeq120
  54. 117:iconst_4
  55. 118:istore4
  56. 120:iload4//将m1(即73行存进去的1)的值入栈
  57. 122:tableswitch{//0to4
  58. 0:156
  59. 1:161//这里走1这个分支,跳到161
  60. 2:166
  61. 3:171
  62. 4:176
  63. default:181
  64. }
  65. 156:iconst_1
  66. 157:istore_2
  67. 158:goto184
  68. 161:iconst_2//将2入栈
  69. 162:istore_2//将2存储到value
  70. 163:goto184//跳转到184进行打印输出
  71. 166:iconst_3
  72. 167:istore_2
  73. 168:goto184
  74. 171:iconst_4
  75. 172:istore_2
  76. 173:goto184
  77. 176:iconst_5
  78. 177:istore_2
  79. 178:goto184
  80. 181:bipush6
  81. 183:istore_2
  82. 184:getstatic#9//Fieldjava/lang/System.out:Ljava/io/PrintStream;
  83. 187:iload_2
  84. 188:invokevirtual#10//Methodjava/io/PrintStream.println:(I)V
  85. 191:return

在这一堆指令中我们发现,jdk1.7并没有新指令来处理string switch,还是继续用lookupswitch和tableswitch两个指令来处理的,也没有扩展这两个指令,它们还是只能处理int。

在11行,我们看到调用了需要switch的string的hashCode方法,并对该hashCode进行switch,并跳转到相应的处理指令。跳到新的指令处我们发现这里(63行)将待switch的变量(name)与case中的值("b")equals了一下。这样做是为了避免不同的string有相同的hashCode,确定equals返回true后,编译器生成了一个处理value的switch,源码里有多少个case编译器生成的tableswitch就有多少个分支,最终会找到相应的处理分支完成string switch的处理。

接下来,看一个不同string hashCode相等的版本:

buzzards与righto的hashCode相等

hierarch与crinolines的hashCode相等

这里选择buzzards和righto

Java代码 收藏代码
  1. publicclassTest{
  2. publicstaticvoidmain(String[]args){
  3. Stringname="buzzards";
  4. intvalue=0;
  5. switch(name){
  6. case"buzzards":
  7. value=1;
  8. break;
  9. case"righto":
  10. value=2;
  11. break;
  12. default:
  13. value=6;
  14. }
  15. System.out.println(value);
  16. }
  17. }

字节码:

Java代码 收藏代码
  1. publicstaticvoidmain(java.lang.String[]);
  2. Code:
  3. 0:ldc#2//Stringbuzzards
  4. 2:astore_1
  5. 3:iconst_0
  6. 4:istore_2
  7. 5:aload_1
  8. 6:astore_3
  9. 7:iconst_m1
  10. 8:istore4
  11. 10:aload_3
  12. 11:invokevirtual#3//Methodjava/lang/String.hashCode:()I
  13. 14:lookupswitch{//1
  14. -931102253:32
  15. default:59
  16. }
  17. 32:aload_3
  18. 33:ldc#4//Stringrighto
  19. 35:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)
  20. 38:ifeq47
  21. 41:iconst_1
  22. 42:istore4
  23. 44:goto59
  24. 47:aload_3
  25. 48:ldc#2//Stringbuzzards
  26. 50:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)
  27. 53:ifeq59
  28. 56:iconst_0
  29. 57:istore4
  30. 59:iload4
  31. 61:lookupswitch{//2
  32. 0:88
  33. 1:93
  34. default:98
  35. }
  36. 88:iconst_1
  37. 89:istore_2
  38. 90:goto101
  39. 93:iconst_2
  40. 94:istore_2
  41. 95:goto101
  42. 98:bipush6
  43. 100:istore_2
  44. 101:getstatic#6//Fieldjava/lang/System.out:Ljava/io/PrintStream;
  45. 104:iload_2
  46. 105:invokevirtual#7//Methodjava/io/PrintStream.println:(I)V
  47. 108:return

这里我们看到两个字符串都跳到32行,首先跟"righto"比较,如果不相等则跳到47行比较buzzards。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值