閱讀871 返回首頁    go 技術社區[雲棲]


Java泛型原理筆記

<T> T 到底是什麼東東

Java泛型的語法相當的別扭,看到一個這樣的寫法,感覺到很神奇,正好研究下Java泛型是怎麼實現的。

public class A{
	public static void main(String[] args) {
		A a = new A();
		a.test();
		
		String r = a.test();
	}
	
	public <T> T test() {
		return (T) new Object();
	}
}


剛開始時,我看到那個"<T> T“ 感覺很神奇,但沒什麼意義。

查看下test()函數生成的字節碼:

public test()Ljava/lang/Object;
   L0
    LINENUMBER 14 L0
    NEW java/lang/Object
    DUP
    INVOKESPECIAL java/lang/Object.<init>()V
    ARETURN
可以發現,這個函數實際上的返回類型是Object,和T沒什麼關係。

再看下調用test()函數的地方對應的字節碼:

		a.test();
		
		String r = a.test();

字節碼:

   L1
    LINENUMBER 5 L1
    ALOAD 1
    INVOKEVIRTUAL A.test()Ljava/lang/Object;
    POP
   L2
    LINENUMBER 7 L2
    ALOAD 1
    INVOKEVIRTUAL A.test()Ljava/lang/Object;
    CHECKCAST java/lang/String
    ASTORE 2
可以看到a.test() 實際上隻是調用了下test()函數,返回值直接被pop掉了,沒有那個T什麼事。

String r = a.test()處,則有個CHECKCAST指令,檢查類型轉換有沒有成功。

所以我們可以看到<T> T這種寫法實際上是一個語法糖,它和下麵這種寫法從本質上來說沒有區別。

public class A{
	public static void main(String[] args) {
		A a = new A();
		a.test();
		
		String r = (String) a.test();
	}
	public Object test() {
		return new Object();
	}
}

extends的情況

下麵再來看個複雜點的例子:

public class A{
	interface interface1{
		public String interfaceOne ();
	}
	public <T extends Date & interface1> T test1(T t) {
		t.interfaceOne();
		t.toLocaleString();
		return null;
	}
}

對應的字節碼分析:

  public test1(Date) : Date
   L0
    LINENUMBER 21 L0
    ALOAD 1: t
    CHECKCAST A$interface1
    INVOKEINTERFACE A$interface1.interfaceOne() : String
    POP
   L1
    LINENUMBER 22 L1
    ALOAD 1: t
    INVOKEVIRTUAL Date.toLocaleString() : String
    POP
   L2
可以看到,用了extends來限定參數的類型後,函數傳進來的參數直接是Date類型的了。

不過,當中間調用到interface1.interfaceOne()時,還是需要一個CHECKCAST來進行類型轉換。

關於CHECKCAST指令

https://www.vmth.ucdavis.edu/incoming/Jasmin/ref--7.html

checkcast checks that the top item on the operand stack (a reference to an object or array) can be cast to a given type. For example, if you write in Java:


return ((String)obj);
then the Java compiler will generate something like:

aload_1                        ; push -obj- onto the stack
checkcast java/lang/String     ; check its a String
areturn                        ; return it
checkcast is actually a shortand for writing Java code like:

if (! (obj == null  ||  obj instanceof <class>)) {
    throw new ClassCastException();
}
// if this point is reached, then object is either null, or an instance of
// <class> or one of its superclasses.
所以CHECKCAST指令實際上和INSTANCEOF指令是很像的,不同的是CHECKCAST如果失敗,會拋出一個異常,INSTANCEOF是返回一個值。

https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html

The instanceof instruction is very similar to the checkcast instruction (§checkcast). It differs in its treatment of null, its behavior when its test fails (checkcast throws an exception,instanceof pushes a result code), and its effect on the operand stack.


另外,據虛擬機專家RednaxelaFX的說法,JVM有可能在運行時優化掉一些CHECKCAST指令。

https://hllvm.group.iteye.com/group/topic/25910


總結:

JAVA的泛型隻是一個語法糖,實際上在運行時還是有類型轉換的過程,從JVM生成的代碼來看,和傳遞一個Object(或者extends的類型)沒什麼區別。當然泛型的最大好處是編繹期的類型錯誤檢查。

明白JAVA泛型的大致實現原理之後,看很多泛型代碼都比較清晰了:)

和C++的泛型比較,C++的泛型是在編繹期實現的,為每一個類型都生成一份代碼,所以C++的泛型容易讓編繹後的代碼出現膨脹。

C++不會保證在運行時,你硬塞一個什麼東東進去函數裏去執行的結果(極有可能程序掛掉了)。

但是Java代碼是跑在JVM裏的,要保證程序無論如何都能正常跑,所以泛型肯定會有CHECKCAST這樣的消耗。

實際上CHECKCAST算是一個運行時的輸入檢查了,而C++沒有這種檢查,Java則要求要這種檢查。而JVM則有可能優化掉這些檢查,比如前麵已經確認過對象的類型了,那麼CHECKCAST就有可能被優化掉。

最後更新:2017-04-03 14:54:18

  上一篇:go 程序猿必看的 幾部電影
  下一篇:go Java IO--壓縮流ZipOutputStream/ZipInputStream