通過字節碼扒一扒java編譯器瞞着我們做了什麼(2)

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並不是跟構造方法同等地位的析構方法。

發佈了80 篇原創文章 · 獲贊 57 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章