通过字节码扒一扒java编译器瞒着我们做了什么(1)

时间:2021-06-18 17:09:08

 1.       Foreach和泛型语法糖

Map<String,String> map = newHashMap<String,String>();

for(Entry<String, String> iter:test.map.entrySet()){

                     Stringstr = iter.getValue();

           }

--看看字节码是什么内容:

      8:aload_1

       9: getfield      #27                 // Field map:Ljava/util/Map;

       12: invokeinterface #44,  1           // InterfaceMethod java/util/Map.e

ntrySet:()Ljava/util/Set;

       17: invokeinterface #48,  1           // InterfaceMethodjava/util/Set.i

terator:()Ljava/util/Iterator;

       22: astore_3

       23: goto          47

       26: aload_3

       27: invokeinterface #54,  1           // InterfaceMethodjava/util/Itera

tor.next:()Ljava/lang/Object;

       32: checkcast     #60                 // class java/util/Map$Entry

       35: astore_2

       36: aload_2

       37: invokeinterface #62,  1           // InterfaceMethod java/util/Map$E

ntry.getValue:()Ljava/lang/Object;

       42: checkcast     #65                 // class java/lang/String

       45: astore        4

       47: aload_3

       48: invokeinterface #67,  1           //InterfaceMethod java/util/Itera

tor.hasNext:()Z

        53: ifne          26

--可以看到红色那些内容,说明实际上编译器真正使用的还是正常的for循环加迭代器遍历的套路,只不过为了让使用者感觉方便创造了foreach这种看似简单易用的语法糖。还有绿色的checkcast,说明Map<String,String> 这个泛型其实也是颗语法糖,跟C++不一样,JVM并不会创建一个Map<String,String>类型,只是编译器在每个使用到Map<String,String>的代码中,进行了类型转化,可以根据InterfaceMethodjava/util/Map$Entry.getValue:()Ljava/lang/Object;可知道iter.getValue()实际返回值只是Object对象,编译器添加了向下转化代码checkcast,所以整个代码经过编译器转化后应该是这样的:String str = (String)iter.getValue();

2.  接口文件的字节码内容

接口文件字节码的内容是什么呢?这是个有意思的问题,首先看到接口文件其实也是生成一份.class文件,再看看字节码经过javap转译后是什么内容:

Classfile/D:/javadevelop/eclipse/user/workspace/RedisDao/bin/RedisDao.class

  Lastmodified 2017-7-19; size 3918 bytes

  MD5checksum 1389d391e77bd4dba7e05d39f38182b9

 Compiled from "RedisDao.java"

public interface RedisDao

  minorversion: 0

 major version: 52

 flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT

Constant pool:

。。。。。。省略

{

 public abstract java.lang.StringbatchSetString(redis.clients.jedis.Jedis, jav

a.lang.String[], boolean);

   descriptor:(Lredis/clients/jedis/Jedis;[Ljava/lang/String;Z)Ljava/lang/Stri

ng;

   flags: ACC_PUBLIC, ACC_ABSTRACT

 

 public abstract java.util.List<java.lang.String>batchGetString(redis.clients.

jedis.Jedis, java.lang.String[]);

   descriptor: (Lredis/clients/jedis/Jedis;[Ljava/lang/String;)Ljava/util/List;

 

   flags: ACC_PUBLIC, ACC_ABSTRACT

   Signature: #10                         // (Lredis/clients/jedis/Jedis;[Ljav

a/lang/String;)Ljava/util/List<Ljava/lang/String;>;

}

--是的,其实跟正常类的字节码布局基本一样,只是方法都被编译器加上了abstract关键字(在源码中并没写该关键字),而且没有CODE段。所以说所谓的“接口”其实本质上就是被编译器阉割(限制)的类而且,就像一个普通人皈依了某宗教,被教律限制了不能做这不能做那,但是对于JVM而言,跟正常类区别不大。另外有特点,如果在定义接口时没有写访问控制权限和抽象关键字,编译器会在生成字节码时在接口前面添加public abstract,所以出现了一个这么特点:你可以不写public abstract,编译器会为你加上,但是你不能写其他限定词,否则编译器就报错不通过。

 

2.       抽象类的字节码内容:

Classfile /D:/javadevelop/eclipse/user/workspace/RedisDao/bin/ChildTest.class

  Last modified 2017-7-20; size 671 bytes

  MD5 checksum 64cf89af9ee1f820eac71c6f59759a78

  Compiled from "ChildTest.java"

public abstractclass ChildTest extends test

  minor version: 0

  majorversion: 52

  flags: ACC_PUBLIC, ACC_SUPER, ACC_ABSTRACT

Constant pool:

。。。。省略

protected abstractvoid fun();

   descriptor: ()V

   flags: ACC_PROTECTED, ACC_ABSTRACT

。。。。省略

--可见抽象类和正常类的布局 也基本相同,并且抽象类也是独立生成一个.class文件的,所以说“接口”和“抽象类”其实更多是java上的概念,而这个概念其实是对正常类的阉割,这不能做,那不能做,而这个阉割由编译器来实现(如果违反阉割规则编译器不允许通过编译)。但是从字节码角度看,其实相差不大,都是正常的.class布局。

 

3.       从字节码角度看try、catch、finally机制

public void fun(){

            try{

                     Filefile = new File("");

            }catch(Exceptione){

                     e.printStackTrace();

                     return;

            }finally{

                     ((Throwable) e).getMessage();

            }

}

--这段代码编译成字节码:

 public void fun();

   descriptor: ()V

   flags: ACC_PUBLIC

   Code:

     stack=3, locals=3, args_size=1

         0: new           #34                 // class java/io/File

         3: dup

         4: ldc           #36                 // String

         6: invokespecial #38                 // Methodjava/io/File."<init>":(L

java/lang/String;)V

         9: astore_1

        10: goto          44

        13: astore_1

        14: aload_1

        15: invokevirtual #41                 // Methodjava/lang/Exception.prin

tStackTrace:()V

        18: aload_0

        19: getfield      #46                 // Field e:Ljava/lang/Object;

        22: checkcast     #50                 // class java/lang/Throwable

        25: invokevirtual #52                 // Methodjava/lang/Throwable.getM

essage:()Ljava/lang/String;

        28: pop

        29: return

        30: astore_2

        31: aload_0

        32: getfield      #46                 // Field e:Ljava/lang/Object;

        35: checkcast     #50                 // class java/lang/Throwable

        38: invokevirtual #52                 // Methodjava/lang/Throwable.getM

essage:()Ljava/lang/String;

        41: pop

        42: aload_2

        43: athrow

        44: aload_0

        45: getfield      #46                 // Field e:Ljava/lang/Object;

        48: checkcast     #50                 // class java/lang/Throwable

        51: invokevirtual #52                 // Methodjava/lang/Throwable.getM

essage:()Ljava/lang/String;

        54: pop

        55: return

      Exception table:

         from   to  target type

             0    10   13   Class java/lang/Exception

             0    18   30   any

     LineNumberTable:

       line 28: 0

       line 29: 10

       line 30: 14

       line 33: 18

       line 31: 29

       line 32: 30

       line 33: 31

       line 34: 42

       line 33: 44

       line 35: 55

     LocalVariableTable:

       Start  Length  Slot Name   Signature

           0      56    0  this   LChildTest;

          14      16     1    e   Ljava/lang/Exception;

     StackMapTable: number_of_entries = 3

       frame_type = 77 /* same_locals_1_stack_item */

         stack = [ class java/lang/Exception ]

       frame_type = 80 /* same_locals_1_stack_item */

         stack = [ class java/lang/Throwable ]

        frame_type = 13 /* same */

如果运行时不抛出异常则按绿色标识的CODE段的正常流程走,看看正常流程是怎么走的:1.执行代码File file = new File("");,2. Goto 第44字节开始的命令,3.第44字节开始的命令就是((Throwable) e).getMessage();

如果运行时抛出异常呢?注意红色那段,这是Exception table,当运行时try模块抛出异常后,第一步就是去查询Exception table,红色那两行是什么意思呢?第一行意思就是当从0到10字节中抛出类型为Exception或它的子类异常时,则指令应该跳转到第13字节指令,第13字节开始的指令就是catch模块的e.printStackTrace();,顺着执行,然后执行finally模块中的代码。最后才执行catch模块的return。第二行意思就是当从0到18字节指令也就是try和catch两个模块中抛出任何其他类型的异常时,第一步跳转到第30字节指令,也就是finally模块的代码。

这样一来就保证了无论如何finally模块的代码都必须执行,并且在return之前执行。

如果把finally模块修改下:

finally{

                     thrownew Exception("zrf");

           }

则字节码如下:

 public void fun() throws java.lang.Exception;

   descriptor: ()V

   flags: ACC_PUBLIC

Exceptions:

     throws java.lang.Exception

       22: new           #35                 // class java/lang/Exception

       25: dup

       26: ldc           #47                 // String zrf

       28: invokespecial #49                // Method java/lang/Exception."<in

it>":(Ljava/lang/String;)V

        31: athrow

--可见在fun方法的字节码中增加了一个Exceptions: throws java.lang.Exception段,而且在finally模块指令中执行athrow指令