java7(1)——反编译深入理解增强的switch(读字节命令实战)
【本文介绍】
本文主要讲java_7 的改进switch的底层实现。反编译一个使用带String的switch的demo并一步步解析反编译出来的字节命令,从编译的角度解读switch的底层实现。
【正文】
在java7中,switch()可以放进去String 类型了,这无非是一大便利。底层JVM的swtich并没有真正的改进,只是在编译阶段,编译器把关于String的switch拆分成if语句而已。
我们写一个简单的例子测试一下:
(1)Test类:switch()使用String
1 public class Test { 2 3 public void test(String str) { 4 5 switch(str){ 6 7 case "a": System.out.println("a");break; 8 case "b": System.out.println("b");break; 9 default : System.out.println("default"); 10 11 } 12 } 13 }
(2)Test2类:switch()使用int
public class Test2 { public void test(int str) { switch(str){ case 1: System.out.println("1");break; case 2: System.out.println("2");break; default : System.out.println("default"); } } }
javac 编译 , javap -c 反编译 Test 后的结果:
public class Test { public Test(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>": ()V 4: return public void test(java.lang.String); Code: 0: aload_1 1: astore_2 ---------------从这里开始------------ 2: iconst_m1 // 将int型-1推送至栈顶 3: istore_3 // 赋值,因为此时栈顶元素为-1,所以赋值-1 4: aload_2 5: invokevirtual #2 // Method java/lang/String.hashCode: 调用hasCode方法 ()I 8: lookupswitch { // 2 源码本来只有一次switch,现在被拆分成两次,这是第一次switch,下面还有一次公共的 97: 36 case 97 : 跳至36行 aload_2 98: 50 case 98 :跳至50行 aload_2 default: 61 default : 跳至61行 iload_3 } 36: aload_2 37: ldc #3 // String a 下面equal的内容 39: invokevirtual #4 // Method java/lang/String.equals:(L 进行equal的比较 java/lang/Object;)Z 42: ifeq 61 // if 语句 45: iconst_0 // 将int型0推送至栈顶 46: istore_3 // 赋值,因为此时栈顶元素为 0 ,所以赋值0 47: goto 61 50: aload_2 51: ldc #5 // String b 下面equal的内容 53: invokevirtual #4 // Method java/lang/String.equals:(L 进行equal的比较 java/lang/Object;)Z 56: ifeq 61 // if 语句 59: iconst_1 // 将int型1推送至栈顶 60: istore_3 // 赋值,因为此时栈顶元素为 1 , 所以赋值1 61: iload_3 ----------------到这里结束--------------- 62: lookupswitch { // 2 0: 88 1: 99 default: 110 } 88: getstatic #6 // Field java/lang/System.out:Ljava/ io/PrintStream; 91: ldc #3 // String a 93: invokevirtual #7 // Method java/io/PrintStream.printl n:(Ljava/lang/String;)V 96: goto 118 99: getstatic #6 // Field java/lang/System.out:Ljava/ io/PrintStream; 102: ldc #5 // String b 104: invokevirtual #7 // Method java/io/PrintStream.printl n:(Ljava/lang/String;)V 107: goto 118 110: getstatic #6 // Field java/lang/System.out:Ljava/ io/PrintStream; 113: ldc #8 // String default 115: invokevirtual #7 // Method java/io/PrintStream.printl n:(Ljava/lang/String;)V 118: return }
javac 编译 , javap -c 反编译 Test2 后的结果:
public class Test2 { public Test2(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>": ()V 4: return public void test(int); Code: 0: iload_1 1: lookupswitch { // 2 1: 28 2: 39 default: 50 } 28: getstatic #2 // Field java/lang/System.out:Ljava/ io/PrintStream; 31: ldc #3 // String 1 33: invokevirtual #4 // Method java/io/PrintStream.printl n:(Ljava/lang/String;)V 36: goto 58 39: getstatic #2 // Field java/lang/System.out:Ljava/ io/PrintStream; 42: ldc #5 // String 2 44: invokevirtual #4 // Method java/io/PrintStream.printl n:(Ljava/lang/String;)V 47: goto 58 50: getstatic #2 // Field java/lang/System.out:Ljava/ io/PrintStream; 53: ldc #6 // String default 55: invokevirtual #4 // Method java/io/PrintStream.printl n:(Ljava/lang/String;)V 58: return }
大家看到这么多字节码是不是有点头晕不想再看下去了?其实只需稍稍观察比较就能发现”从这里开始“——”到这里结束“中间那些字节码是下面那个字节码文件所没有的,所以我们研究这几行代码就行了。又看我用红色字体标出来的注释,结果就显而易见了:
(0)用一个int类型变量代表String类型变量
(1)获取String字符串的hashCode
(2)case hashCode
(3)用if语句处理String
(4)为int类型的变量赋值
(5)真正的swtich,现在传入的是上面得出的int类型变量。
把上面的字节码文件翻译成java即:
1 public class test { 2 3 public void test(String str) { 4 5 int i = -1; 6 7 switch(str.hashCode()){ 8 9 case 97: 10 if(str.equals("a")){ 11 i = 0; 12 } 13 break; 14 case 98: 15 if(str.equals("b")){ 16 break; 17 } 18 } 19 20 switch(i) { 21 22 case 0: 23 System.out.println("a"); 24 break; 25 26 case 1: 27 System.out.println("b"); 28 break; 29 30 default: 31 System.out.println("default"); 32 } 33 } 34 }