程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 對Java多態性綜合運用的探討

對Java多態性綜合運用的探討

編輯:關於JAVA

或許大家java的多態問題,對上溯,下溯造型有了一定的概念,對protect和private大家想必也很清楚,但是,這幾個個結合在一起,往往令人產生很多困惑,在這裡,我舉一個例子,大家或許會發覺這篇文章對你來說還是很有意義的:

例子一共有兩個class. 可能出現困惑的地方我都會在後面一一解釋。

A是一個父類,B繼承A,並且實現了protectedTest(Object obj)方法.如下面所示:

B.java的源代碼:

package cn.org.matrix.test;
import cn.org.matrix.test.A;
/**
* <p>Title: protect, private and upcasting </p>
* <p>Description: email:[email protected]</p>
* <p>Copyright: Matrix Copyright (c) 2003</p>
* <p>Company: Matrix.org.cn</p>
* @author chris
* @version 1.0,who use this example pls remain the declare
*/
public class B extends A
{
protected int protectedb = 0;
protected int protectedab = 0;
protected void protectedTest(Object obj)
{
System.out.println("in B.protectedTest(Object):" + obj);
}
}

A.java的源代碼:

package cn.org.matrix.test;
import cn.org.matrix.test.B;
/**
* <p>Title: protect, private and upcasting </p>
* <p>Description: email:[email protected]</p>
* <p>Copyright: Matrix Copyright (c) 2003</p>
* <p>Company: Matrix.org.cn</p>
* @author chris
* @version 1.0,who use this example pls remain the declare
*/
public class A
{
protected int protecteda = 0;
protected int protectedab = 0;
private void privateTest()
{
System.out.println("in A.privateTest()");
}
protected void protectedTest(Object obj)
{
System.out.println("in A.protectedTest(Object):" + obj );
}
protected void protectedTest( String str )
{
System.out.println("in A.protectedTest(String):" + str);
}
public static void main (String[] args)
{
// Test A
A a1 = new A();
a1.privateTest();
// Test B
String helloStr = "Hello";
Object helloObj = helloStr;
B b1 = new B();
A a2 = b1; // 這裡發生了什麼?困惑1
b1=a1; //編譯錯誤,困惑2
b1. privateTest(); //編譯錯誤,困惑3
b1.protectedTest(helloObj); //輸出結果?困惑4
b1.protectedTest(helloStr); //編譯錯誤,困惑5
a2.protectedTest(helloObj); //輸出結果? 困惑6
a2.protectedTest(helloStr); //輸出結果?困惑7 ?
}
}

下面,我來逐個解釋每一處困惑的地方:

困惑1:

這裡其實就是子類自動上溯造型到父類A。這裡a2其實是指向了一個B類型的對象. 我們通常都可以這樣作: A a2=b1, 這樣作的意思實際上就是讓a2指向了一個類型B的對象-在這裡就是b1了。

在java裡面,關於跨類引用,有兩條規則應該記住:

1. 如果a是類A的一個引用,那麼,a可以指向類A的一個實例,或者說指向類A的一個子類。

2. 如果a是接口A的一個引用,那麼,a必須指向實現了接口A的一個類的實例。

所以,根據這兩個規則,我們就不難理解例子中的A a2 = b1是什麼意思了。

困惑2:

A a2 = b1是可以的,但是為什麼b1=a1卻是不行? 在這裡,我們依然可以套用上面的兩條規則,我們可以看到,b1是類B的一個引用,a1既不是類B的實例,也不是類B的子類的實例,所以直接b1=a1就出現了編譯錯誤.

如果確實需要進行這樣的轉化,我們可以這樣作:b1=(B)a1; 進行強制轉化,也就是下溯造型. 在java裡面,上溯造型是自動進行的,但是下溯造型卻不是,需要我們自己定義強制進行.

困惑3:

b1. privateTest();編譯不通過? 這是很顯然的,你可以回顧一下private的定義: 私有域和方法只能被定義該域或方法的類訪問. 所以,在這裡,b1不能訪問A的方法privateTest(),即使b1是A的子類的實例.

請看下面的例子:

public class A
{
private int two(int i) { return i; }
}
class Test extends A {
public static void main(String[] args) {
System.out.println(A.two(3));
}
}

System.out.println(A.two(3));這行編譯出錯,顯然,因為private方法不能在這個類之外被訪問。

而protected則不同,我們回顧一下protected的定義: 被保護的域或方法只能被類本身、類的子類和同一 程序包中的類所訪問。

下面是一個錯誤使用protected的例子:

package cn.org.matrix.test;
public class ProtectedTest {
protected void show() {
System.out.println("I am in protected method");
}
}
import cn.org.matrix.test.*;
public class Test {
public static void main (String[] args) {
ProtectedTest obj = new ProtectedTest();
obj.show();
}
}

因為訪問權限問題,你會得到"show() has protected access in test.ProtectedTest"的出錯信息.

困惑4:

b1.protectedTest(helloObj); 輸出的是"in B.protectedTest(Object):…" 這到底是為什麼呢? 為什麼jvm能夠確定是輸出B的方法而不是A的方法? 這就和jvm的運行機制有關系了. 我們上面提到了,a1是一個A類型的引用,但是指向了一個B類型的實例. 在這裡,如果jvm根據引用的類型-在這裡就是A 來定義調用哪個方法的話,那麼應該是調用A的protectedTest(helloObj).

然後實際上不是這樣的,因為jvm的動態編譯能力,jvm會在run-time來決定調用哪一個method,而不是在compile time. 也就是所謂的late-binding(run-time)和early-binding(compile-time).

困惑5:

b1.protectedTest(helloStr); 這裡為什麼會出現編譯錯誤? 他可以調用類B的protectedTest(Object obj)方法啊,把helloStr上溯造型成一個object就行了啊..或者上溯造型到A然後調用A的protectedTest(helloStr)方法。

問題的根源就在於此了,既然有兩種選擇,jvm應該選擇那一種?這種不確定性如果交給jvm來動態決定的話,勢必帶來程序的不確定性..雖然java在其他的一些地方也有類似的情形出現,比如static變量的循環定義造成的不確定性,但是,在這裡,jvm還是在編譯階段就解決了這個問題。

所以,我們會在這一步遇到編譯錯誤: "reference to protectedTest is ambiguous; both method protectedTest(java.lang.String) in mytest.A and method protectedTest(java.lang.Object) in mytest.B match at line 46.

在這裡,我們遇到的是顯式的reference ambiguous錯誤,但是,有時候,隱式的reference ambiguous卻往往是更加的危險。

在這裡,我舉個例子:

父類的 源代碼:

public super
{
private void test(int i, long j);
{
System.out.println(i+"and"+j);
}
}

子類的源代碼:

public sub
{
private void test(long j, int i);
{
System.out.println(i+"and"+j);
}
}

子類和父類都用有相同名稱的方法test,參數類型不同而已.這種情況下,編譯可以被通過.

但是如果你在另外一個類中用到了如下代碼:

Sub sb = new Sub();

sb.test(100, 3000);

你就會遇到編譯錯誤,因為沒有確定的指出3000的類型,所以造成reference ambiguous的錯誤了.

困惑6:

a2.protectedTest(helloObj);

輸出結果分別是:"in B.protectedTest(Object).." 經過上面的解釋,想必大家都能很清楚的知道為什麼會有這兩個輸出結果了:a2.protectedTest(helloObj);因為jvm的late-binding,所以在run-time的時候,調用了B類的方法,雖然在編譯期間a2只是一個父類A的引用類型。

困惑7:

a2.protectedTest(helloStr); 為什麼這裡會輸出" in A.protectedTest(Object)…"。為什麼這裡不會編譯出錯?為什麼b1. protectedTest(helloStr)會出錯而a2. protectedTest(helloStr)會出錯?我調用了a2.equals(b1)和a2==b1得到的結果都是true啊?但是為什麼這裡出這個錯誤?

在這裡,這個問題是最關鍵的,也是我們放到最後來解答的原因。

首先,回顧一下equals()和==的在java裡面的概念,記得有一道scjp的題目:

題目:下面的哪些敘述為真。

A. equals()方法判定引用值是否指向同一對象。

B. == 操作符判定兩個分立的對象的內容和類型是否一致。

C. equals()方法只有在兩個對象的內容一致時返回true。

D. 類File重寫方法equals()在兩個分立的對象的內容和類型一致時返回true。

答案是AD,嚴格來說這個問題的答案是不確定的,因為equals()方法是可以被重載的,但是如果新類沒有重寫equals(),則該方法在兩個變量指向同一對象時返回真. 實際上java也是推薦的是使用equals()方法來判斷兩個對象的內容是否一樣,就像String類的equals()方法所做的那樣,判定兩個String對象的內容是否相同。而==操作符返回true的唯一條件是兩個變量指向同一對象。

在這裡,我們不再深入的討論關於equals()和==的區別和概念。我們只需要知道,在我們的例子裡面,無論是equals()和==都是一個含義-就是引用值是否指向同一個對象(因為我們並沒有重寫equals()).

顯然,我們在進行了a2=b1.這一步之後,a2和b1都是指向同一個對象。

既然指向同一個對象,為什麼還要區別對待a2和b1?為什麼a2就沒有編譯錯誤,而b1就要遇到reference ambiguous錯誤?

我們現看看jvm規范裡的一段話:

"The Java Virtual Machine does not require any particular internal

structure for objects. In Sun's current implementation of the Java Virtual Machine, a reference to a class instance is a pointer to a handle that is itself a pair of pointers: one to a table containing the methods of the object and a pointer to the Class object that represents the type of the object, and the other to the memory allocated from the Java heap for the object data."

實際上就是說:在java虛擬機中,類實例的引用就是指向一個句柄(handle)的指針,這個句柄是一對指針:一個指針指向一張表格,實際上這個表格也有兩個指針:一個指針指向一個包含了對象的方法表,另外一個指向類對象;另一個指針指向一塊從java堆中為分配出來內存空間。

那麼,在a2=b1的時候,到底發生了什麼?

實際上,在a2=b1的時候,仍然是存在兩個句柄,a2和b1,但是a2和b1擁有同一塊數據內存塊和不同的函數表。所以在a2.protectedTest(helloStr)的時候,jvm會從a2的函數表裡找到protectedTest(String Str)方法,但是b1.protectedTest(helloStr)卻會出現編譯錯誤,因為jvm從b1的函數表裡找不到,然後就選擇自己上溯造型還是參數上溯造型出現了ambiguous。這也是我們對這個問題的精確解釋。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved