<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指令
http://www.vmth.ucdavis.edu/incoming/Jasmin/ref--7.htmlcheckcast 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是傳回一個值。
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.instanceofThe 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指令。
http://hllvm.group.iteye.com/group/topic/25910總結:
JAVA的泛型隻是一個文法糖,實際上在運作時還是有類型轉換的過程,從JVM生成的代碼來看,和傳遞一個Object(或者extends的類型)沒什麼差別。當然泛型的最大好處是編繹期的類型錯誤檢查。
明白JAVA泛型的大緻實作原理之後,看很多泛型代碼都比較清晰了:)
和C++的泛型比較,C++的泛型是在編繹期實作的,為每一個類型都生成一份代碼,是以C++的泛型容易讓編繹後的代碼出現膨脹。
C++不會保證在運作時,你硬塞一個什麼東東進去函數裡去執行的結果(極有可能程式挂掉了)。
但是Java代碼是跑在JVM裡的,要保證程式無論如何都能正常跑,是以泛型肯定會有CHECKCAST這樣的消耗。
實際上CHECKCAST算是一個運作時的輸入檢查了,而C++沒有這種檢查,Java則要求要這種檢查。而JVM則有可能優化掉這些檢查,比如前面已經确認過對象的類型了,那麼CHECKCAST就有可能被優化掉。