871
技術社區[雲棲]
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