由一道題目引出的java多態

某次逛論壇時發現一個非常有意思的題目,如下:

class A<B> 
		{  
		 	  public String show(A obj)
	         {  
	                return ("A and A");  
	         }   
		 	  public String show(B obj)
		         {  
		                return ("A and B");  
		         }   
		}   
		class B extends A
		{  
		         public String show(B obj)
		         {  
		                return ("B and B");  
		         }  
		         public String show(A obj)
		         {  
		                return ("B and A");  
		         }   
		}  

    A a = new B();  
    B b = new B();  
    System.out.println(a.show(b));  
上面的代碼正確的結果會輸出B and A,剛開始看到的時候也感覺莫名其妙,實際上這裏包含有不少知識點,稍微不留神就會弄錯。題目本身輸出的結果不重要,關鍵是我們要掌握裏面的知識點,明白爲什麼會這樣輸出,最犀利的方式還是從字節碼的角度來觀察。同樣javap命令輸出上面代碼的字節碼。

Compiled from "Test.java"
class A extends java.lang.Object{
A();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public java.lang.String show(A);
  Code:
   0:   ldc     #2; //String A and A
   2:   areturn

public java.lang.String show(java.lang.Object);
  Code:
   0:   ldc     #3; //String A and B
   2:   areturn

Compiled from "Test.java"
class B extends A{
B();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method A."<init>":()V
   4:   return

public java.lang.String show(B);
  Code:
   0:   ldc     #2; //String B and B
   2:   areturn

public java.lang.String show(A);
  Code:
   0:   ldc     #3; //String B and A
   2:   areturn

}

public static void main(java.lang.String[]);
  Code:
   0:   new     #2; //class B
   3:   dup
   4:   invokespecial   #3; //Method B."<init>":()V
   7:   astore_1
   8:   new     #2; //class B
   11:  dup
   12:  invokespecial   #3; //Method B."<init>":()V
   15:  astore_2
   16:  getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   19:  aload_1
   20:  aload_2
   21:  invokevirtual   #5; //Method A.show:(LA;)Ljava/lang/String;
   24:  invokevirtual   #6; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   27:  return

}

上面是類A,類B和main方法的字節碼,先來看下類A的字節碼,可以很神奇的發現show(B obj)這個方法不見了,其實代碼爲了增加複雜性,故意將該方法寫成show(B obj),實際上類A是一個泛型類,這裏用符號B來表示的,可以換成任意的其他符號。衆所周知的是java中泛型是僞泛型,在編譯期會發生一個叫做類型擦除的動作,關於泛型的類型擦除有很多可以講的東西,建議讀者自行去百度一下。因此這裏發生類型擦出後,實際存在的方法爲show(Object obj)。這裏是非常關鍵的一點。

類B的字節碼沒有什麼特殊的內容,只是定義了兩個方法show(B),show(A),不過要注意的是會覆寫類A中的show(A)方法,同時繼承show(Object obj),因此類B中有三個方法。

然後再來看下main方法的字節碼,一步一步的過:

new指令在堆中分配類B需要的內存並初始化成員變量爲默認值,返回執行該地址的指針壓棧。

dup指令複製當前棧頂的元素

invokespecial調用B的初始化函數,消耗一個棧頂元素

astore_1將棧頂元素彈出賦值給局部變量表的第二個變量這裏是A a

然後後面類似的操作

astore_2將棧頂元素彈出賦值給局部變量表的第三個變量這裏是B b

後面三條指令連續三個壓棧操作先壓入out變量,然後是a,最後是b

invokevirtual方法很關鍵,這裏雖然寫的是A.show(A),但是不得不先提及兩個概念

概念一:靜態綁定

靜態綁定指的是在編譯期間就已經確定了要調用的方法,private、static和final修飾的方法都是靜態綁定的,注意在java中只有方法纔有綁定的概念。

概念二:動態綁定

動態綁定指的是在運行時根據對象實際的類型去尋找要調用的方法。JAVA 虛擬機調用一個類方法時(靜態方法),它會基於對象引用的類型(通常在編譯時可知)來選擇所調用的方法。相反,當虛擬機調用一個實例方法時,它會基於對象實際的類型(只能在運行時得知)來選擇所調用的方法,這就是動態綁定,是多態的一種。

介紹完這兩個概念接着看invokevirtual指令,由於引用的類型爲A,因此會首先搜索A的方法表信息,發現show(A)方法最符合,所以這裏編譯的時候綁定到A.show(A),但是在運行中會發生動態綁定,當發現實際對象類型爲B時,會在B的方法表中尋找最合適的方法,如果沒找到則向上尋找父類中合適的方法,這裏由於B覆寫了父類的show(A)方法,因此會調用B的show(A)方法。

以上就是一道題目引出的知識點,包括字節碼的解釋,靜態動態綁定,泛型擦除。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章