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

来源:互联网 发布:误会 知乎 编辑:程序博客网 时间:2024/05/16 15:10

1.      Int[] a={}和int[] a=newint[]{}有何区别?

定义数组时经常会产生一些以为,比如说上面两种数组定义格式是否在就JVM中的实现不同,是否前者没有new所以不会在堆中分配内存?如果不了解编译器私自做了什么,很容易被这个问题困扰住,那我们从编译后的字节码中看看这两种定义形式的实现吧。其实这两种定义的字节码是一样的。比如int[] list = {888,777,999};也就是int[] list = newint[]{888,777,999};

字节码:

15:aload_0

16:iconst_3

17: newarray       int

19: dup

20:iconst_0

21:sipush        888

24:iastore

25: dup

26:iconst_1

27:sipush        777

30:iastore

31: dup

32:iconst_2

33: sipush        999

--注意红色字段,都使用了newarray这个命令,说明两种形式的定义都是在内存中分配内存的。

 

2. 可变参数究竟是什么?

源码:

         publicvoid fun(String ...strs ){

                   System.out.println(strs);

         }

字节码:

 public void fun(java.lang.String...);

   descriptor: ([Ljava/lang/String;)V

   flags: ACC_PUBLIC, ACC_VARARGS

   Code:

     stack=2, locals=2, args_size=2

        0: getstatic     #45                 // Fieldjava/lang/System.out:Ljav

a/io/PrintStream;

        3: aload_1

        4: invokevirtual #51                // Method java/io/PrintStream.prin

tln:(Ljava/lang/Object;)V

        7: return

     LineNumberTable:

       line 31: 0

       line 32: 7

      LocalVariableTable:

        Start Length  Slot  Name  Signature

            0       8    0  this   LChildTest;

            0       8    1  strs   [Ljava/lang/String;

--注意红色部分,这是对局部变量(包括参数)的说明,通过类型签名[Ljava/lang/String可看到其中参数strs明显就是个数组。

 

3. 跳转类关键字的字节码实现机制

If,continue,break,for,while,goto

--这些跳转关键字本质上在字节码中都是goto指令,形式:goto xx (xx为字节码命令的偏移量)

有个例外switch命令不是用goto指令,而是用了tableswitch(通过索引访问跳转表,并跳转)

1: tableswitch   { // 1 to 2

              1: 24

              2: 30

        default: 30

     }

 

4.从字节码角度看向上转型和向下转型

向上转型源码:

publicint fun(String str){

                   Object o = (Object)str;

                   o.toString();

                   return 0;

         }

向上转型字节码:

    Code:

      stack=1, locals=3, args_size=2

         0: aload_1

         1: astore_2

         2: aload_2

         3: invokevirtual #39                 // Methodjava/lang/Object.toStrin

g:()Ljava/lang/String;

--从字节码内容可以看出,由于向上转型是“安全”的,所以字节码并没有作任何转化的命令操作

向下转型源码:

publicint fun(Object str){

                   String o = (String)str;

                   o.toString();

                   return 0;

         }

向下转型字节码:

    Code:

      stack=1, locals=3, args_size=2

         0: aload_1

         1: checkcast     #39                 // class java/lang/String

         4: astore_2

         5: aload_2

         6: invokevirtual #41                 // Methodjava/lang/String.toStrin

g:()Ljava/lang/String;

--注意红色部分,由于向下转型可能是不安全的,编译器增加了一个类型检查指令,避免不正确的转换。这样,运行时发现不安全的向下转换,则抛出java.lang.Long cannot be cast to java.lang.String异常。

 

5.从字节码角度看finalize方法和C++析构函数的区别

很多从C++转java的人会一开始把java中的finalize方法等同于C++的析构函数,但是其实两者是很不同的,finalize方法某种程度而言就是普通的成员方法,只不过这个方法被编译器固定为无参无返回的方法,至于其它的编译器不再像构造方法那样私自添加任何指令。还有一点特殊的是JVM回收该类型对象中会先调用finalize方法。除了这两点外,finalize就是一个普通方法,而非跟构造方法地位平等的析构方法。比如C++会在程序员没定义但是代码需要时让编译器自动添加析构函数,并且会先调用父类的析构函数。而java中的finalize是完全没有这个特权的,编译器不会为任何类自动生成finalize方法,并且也不会像构造方法那样在子类的finalize方法中自动调用父类的finalize方法,这些从字节码中可以显而易见。

源码:

public class test {

         protectedvoid finalize(){

                   System.out.println("finalize");

         }

}

 

public class ChildTest extends test {

         protectedvoid finalize(){

                   System.out.println("ChildTestfinalize");

         }

}

ChildTest字节码:

 protected void finalize();

   descriptor: ()V

   flags: ACC_PROTECTED

   Code:

     stack=2, locals=1, args_size=1

        0: getstatic     #41                 // Fieldjava/lang/System.out:Ljav

a/io/PrintStream;

        3: ldc           #62                 // String ChildTest finalize

        5: invokevirtual #64                // Method java/io/PrintStream.prin

tln:(Ljava/lang/String;)V

        8: return

--使得,可以看到,子类的finalize方法并没有任何调用父类的finalize指令。所以说finalize并不是跟构造方法同等地位的析构方法。

阅读全文
0 0
原创粉丝点击