在下面的程序中,子類的一個(gè)域具有與超類的一個(gè)域相同的名字。那么,這個(gè)程序會(huì)打印出什么呢?
class Base {
public String className = "Base";
}
class Derived extends Base {
private String className = "Derived";
}
public class PrivateMatter {
public static void main(String[ ] args) {
System.out.println(new Derived().className);
}
}
對(duì)該程序的表面分析可能會(huì)認(rèn)為它應(yīng)該打印Derived,因?yàn)檫@正是存儲(chǔ)在每一個(gè)Derived實(shí)例的className域中的內(nèi)容。
更深入一點(diǎn)的分析會(huì)認(rèn)為Derived類不能編譯,因?yàn)镈erived中的className變量具有比Base中的className變量更具限制性的訪問權(quán)限。
如果你嘗試著編譯該程序,就會(huì)發(fā)現(xiàn)這種分析也不正確。該程序確實(shí)不能編譯,但是錯(cuò)誤卻出在PrivateMatter中。
如果className是一個(gè)實(shí)例方法,而不是一個(gè)實(shí)例域,那么Derived.className()將覆寫B(tài)ase.className(),而這樣的程序是非法的。一個(gè)覆寫方法的訪問修飾符所提供的訪問權(quán)限與被覆寫方法的訪問修飾符所提供的訪問權(quán)限相比,至少要一樣多[JLS 8.4.8.3]。
因?yàn)閏lassName是一個(gè)域,所以Derived.className隱藏(hide)了Base.className,而不是覆蓋了它[JLS 8.3]。對(duì)一個(gè)域來說,當(dāng)它要隱藏另一個(gè)域時(shí),如果隱藏域的訪問修飾符提供的訪問權(quán)限比被隱藏域的少,盡管這么做不可取的,但是它確實(shí)是合法的。事實(shí)上,對(duì)于隱藏域來說,如果它具有與被隱藏域完全無關(guān)的類型,也是合法的:即使Derived.className是GregorianCalendar類型的,Derived類也是合法的。
在我們的程序中的編譯錯(cuò)誤出現(xiàn)在PrivateMatter類試圖訪問Derived.className的時(shí)候。盡管Base有一個(gè)公共域className,但是這個(gè)域沒有被繼承到Derived類中,因?yàn)樗籇erived.className隱藏了。在Derived類內(nèi)部,域名className引用的是私有域Derived.className。因?yàn)檫@個(gè)域被聲明為是private的,所以它對(duì)于PrivateMatter來說是不可訪問的。因此,編譯器產(chǎn)生了類似下面這樣的一條錯(cuò)誤信息:
PrivateMatter.java:11: className has private access in Derived
System.out.println(new Derived().className);
^
請(qǐng)注意,盡管在Derived實(shí)例中的公共域Base.className被隱藏了,但是我們還是可以通過將Derived實(shí)例轉(zhuǎn)型為Base來訪問到它。下面版本的PrivateMatter就可以打印出Base:
public class PrivateMatter {
public static void main(String[] args) {
System.out.println(((Base)new Derived()).className);
}
}
這說明了覆寫與隱藏之間的一個(gè)非常大的區(qū)別。一旦一個(gè)方法在子類中被覆寫,你就不能在子類的實(shí)例上調(diào)用它了(除了在子類內(nèi)部,通過使用super關(guān)鍵字來方法)。然而,你可以通過將子類實(shí)例轉(zhuǎn)型為某個(gè)超類類型來訪問到被隱藏的域,在這個(gè)超類中該域未被隱藏。
如果你想讓這個(gè)程序打印Derived,也就是說,你想展示覆寫行為,那么你可以用公共方法來替代公共域。在任何情況下,這都是一個(gè)好主意,因?yàn)樗峁┝烁玫姆庋b[EJ Item 19]。下面的程序版本就使用了這項(xiàng)技術(shù),并且能夠打印出我們所期望的Derived:
class Base {
public String getClassName() {
return "Base";
}
}
class Derived extends Base {
public String getClassName() {
return "Derived";
}
}
public class PrivateMatter {
public static void main(String[] args) {
System.out.println(new Derived().getClassName());
}
}
請(qǐng)注意,我們將Derived類中的getClassName方法聲明成了public的,盡管在最初的程序中與其相對(duì)應(yīng)的域是私有的。就像前面提到的那樣,覆寫方法的訪問修飾符與它要覆寫的方法的訪問修飾符相比,所具有的限制性不能有任何降低。
class Base {
public String className = "Base";
}
class Derived extends Base {
private String className = "Derived";
}
public class PrivateMatter {
public static void main(String[ ] args) {
System.out.println(new Derived().className);
}
}
對(duì)該程序的表面分析可能會(huì)認(rèn)為它應(yīng)該打印Derived,因?yàn)檫@正是存儲(chǔ)在每一個(gè)Derived實(shí)例的className域中的內(nèi)容。
更深入一點(diǎn)的分析會(huì)認(rèn)為Derived類不能編譯,因?yàn)镈erived中的className變量具有比Base中的className變量更具限制性的訪問權(quán)限。
如果你嘗試著編譯該程序,就會(huì)發(fā)現(xiàn)這種分析也不正確。該程序確實(shí)不能編譯,但是錯(cuò)誤卻出在PrivateMatter中。
如果className是一個(gè)實(shí)例方法,而不是一個(gè)實(shí)例域,那么Derived.className()將覆寫B(tài)ase.className(),而這樣的程序是非法的。一個(gè)覆寫方法的訪問修飾符所提供的訪問權(quán)限與被覆寫方法的訪問修飾符所提供的訪問權(quán)限相比,至少要一樣多[JLS 8.4.8.3]。
因?yàn)閏lassName是一個(gè)域,所以Derived.className隱藏(hide)了Base.className,而不是覆蓋了它[JLS 8.3]。對(duì)一個(gè)域來說,當(dāng)它要隱藏另一個(gè)域時(shí),如果隱藏域的訪問修飾符提供的訪問權(quán)限比被隱藏域的少,盡管這么做不可取的,但是它確實(shí)是合法的。事實(shí)上,對(duì)于隱藏域來說,如果它具有與被隱藏域完全無關(guān)的類型,也是合法的:即使Derived.className是GregorianCalendar類型的,Derived類也是合法的。
在我們的程序中的編譯錯(cuò)誤出現(xiàn)在PrivateMatter類試圖訪問Derived.className的時(shí)候。盡管Base有一個(gè)公共域className,但是這個(gè)域沒有被繼承到Derived類中,因?yàn)樗籇erived.className隱藏了。在Derived類內(nèi)部,域名className引用的是私有域Derived.className。因?yàn)檫@個(gè)域被聲明為是private的,所以它對(duì)于PrivateMatter來說是不可訪問的。因此,編譯器產(chǎn)生了類似下面這樣的一條錯(cuò)誤信息:
PrivateMatter.java:11: className has private access in Derived
System.out.println(new Derived().className);
^
請(qǐng)注意,盡管在Derived實(shí)例中的公共域Base.className被隱藏了,但是我們還是可以通過將Derived實(shí)例轉(zhuǎn)型為Base來訪問到它。下面版本的PrivateMatter就可以打印出Base:
public class PrivateMatter {
public static void main(String[] args) {
System.out.println(((Base)new Derived()).className);
}
}
這說明了覆寫與隱藏之間的一個(gè)非常大的區(qū)別。一旦一個(gè)方法在子類中被覆寫,你就不能在子類的實(shí)例上調(diào)用它了(除了在子類內(nèi)部,通過使用super關(guān)鍵字來方法)。然而,你可以通過將子類實(shí)例轉(zhuǎn)型為某個(gè)超類類型來訪問到被隱藏的域,在這個(gè)超類中該域未被隱藏。
如果你想讓這個(gè)程序打印Derived,也就是說,你想展示覆寫行為,那么你可以用公共方法來替代公共域。在任何情況下,這都是一個(gè)好主意,因?yàn)樗峁┝烁玫姆庋b[EJ Item 19]。下面的程序版本就使用了這項(xiàng)技術(shù),并且能夠打印出我們所期望的Derived:
class Base {
public String getClassName() {
return "Base";
}
}
class Derived extends Base {
public String getClassName() {
return "Derived";
}
}
public class PrivateMatter {
public static void main(String[] args) {
System.out.println(new Derived().getClassName());
}
}
請(qǐng)注意,我們將Derived類中的getClassName方法聲明成了public的,盡管在最初的程序中與其相對(duì)應(yīng)的域是私有的。就像前面提到的那樣,覆寫方法的訪問修飾符與它要覆寫的方法的訪問修飾符相比,所具有的限制性不能有任何降低。