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