天天看點

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指令

http://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是傳回一個值。

http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.instanceof

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指令。

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就有可能被優化掉。