本章更多討論了子類對父類的繼承可能導致的各種陷阱,比如隱藏(hidden),遮蔽(shadow),遮掩(obscure),覆寫(override),重載(overload)等行為。
1。首先來看看一個隱藏的例子:
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);
}
}
我們可能指望它打印Base,可很抱歉,此程序是無法編譯通過,剛一看錯誤信息你可能愣?。?/p>
無權訪問private的className。。。
對于實例方法,子類可以對父類的實例方法進行覆寫,可對于實例變量(而且包括類變量,靜態(tài)方法,final靜態(tài)變量),子類只能隱藏父類中的相同名稱的變量,而不是覆寫。根據newDerived()的編譯期類型為子類Derived,調用子類的className屬性,可此屬性是聲明為private的,所以無法編譯通過。如果我們想調用父類中被隱藏的className,可以通過向上轉型來實現:
System.out.println(((Base)new Derived()).className);
此例告訴我們,JAVA語言中子類定義與父類相同類型和名稱的變量、嵌套類型和靜態(tài)方法,都將隱藏掉父類中相應的方法,而不是覆寫,所以,請避免隱藏!此過程中當然也不存在所謂多態(tài)。另外,我們不應該違反這樣一條規(guī)則:對基類所做的任何行為,都應當可以同樣作用于子類。此例中子類className為private,違反了基類中className是public的定義,這樣的寫法應該避免。
2。也許哪一天你突然想自己寫一個String來代替java.lang中的String,讓我們來看看會發(fā)生什么?
public class StrungOut {
public static void main(String[] args) {
String s = new String("Hello world");
System.out.println(s);
}
}
class String {
private final java.lang.String s;
public String(java.lang.String s) {
this.s = s;
}
public java.lang.String toString() {
return s;
}
}
試運行此程序,JVM會給你一個非常奇怪的消息:
StrungOut dose not have a main method!
怪了,明明有個main方法啊??請注意,main方法中的參數String []args,其中的String類型要求是java.lang.String,可JVM會自動地把把這些參數認為是我們自定義的下面那個String類型,這就是錯誤的原因所在。教訓:避免重用類名,特別是java平臺的類型,特別是java.lang包下的類名!在此例中,當前類所在包中的所有類的main方法都將因此失效。
3。遮掩(obscure):我覺的翻譯成模糊也許更好??纯聪旅娴睦樱?/p>
public class ShadesOfGray {
public static void main(String[] args){
System.out.println(X.Y.Z);
}
}
class X {
static class Y {
static String Z = "Black";
}
static C Y = new C();
}
class C {
String Z = "White";
}
你認為他應該打印什么呢??黑還是白?還是黑白不分:),光明的力量總是偉大,它一直打印的是:white。這說明了X.Y一直調用的是靜態(tài)變量Y,而不是靜態(tài)內隱類Y。JAVA語言規(guī)范告訴我們,當一個變量和一個類型具有相同的名字,明確他們位于相同的作用范圍內,變量名具有優(yōu)先權,同樣,變量名與類型名將遮掩包名。
即變量名>類型名>包名。其實上面的例子有更嚴重的問題,它并沒有遵循標準的JAVA命名習慣,變量應該以小寫開頭(mixedCase的格式),類名以大寫開頭(MaxedCase的格式),如果完全遵照習慣來寫,就不會出現此問題了。所以,請遵守標準的命名習慣。退一步,假設在某些情況下我們只能以此方式書寫,那我們怎么訪問靜態(tài)內隱類Y呢?兩種方法:
System.out.println(((X.Y)null).Z); //借助表達式訪問類變量,還記的嗎?
在JDK5中還可以這樣:
public static <T extends X.Y> void main(String args[]){ //繼承X.Y類解決此問題。
System.out.println(T.Z);
}
4。包A中的某個類被另一個包C中的子類所繼承,如果子類當中“覆寫”了父類中的方法,而此方法在父類中不是聲明為public或者protected,那么這并非覆寫,這兩個方法將沒有任何關系。所以,如果你希望某個類的一個方法被包外的子類所覆寫,請把此方法聲明為protected或者public。
5。遮蔽(shadow),這里討論了JDK5靜態(tài)導入需要注意的問題,看下面的例子:
import static java.util.Arrays.toString;
class ImportDuty {
public static void main(String[] args) {
printArgs(1, 2, 3, 4, 5);
}
static void printArgs(Object... args) {
System.out.println(toString(args));
}
}
這個例子表面上看起來很正常,可事實上是無法編譯通過的,編譯器告訴我們,找不到恰當的toString()方法。這是為何?我們明明已經導入了Arrays.toString方法了啊?應該打印:[1,2,3,4,5]才對!這是因為編譯器將首先在類ImportDuty的范圍內尋找toString方法,這個方法將從Object類繼承而來的toString()方法,它并不能接受參數Object []args!這就是原因所在,某個范圍內的成員對比于靜態(tài)導入的具有優(yōu)先權。也就是類ImportDuty的toString()方法遮蔽了Arrays.toString(Object []args)方法。遮蔽與遮掩的區(qū)別在于,遮蔽的只能是同類型的名稱,而遮掩的是不同類型的(如變量名遮掩類名,類名遮掩包名)。慎重使用靜態(tài)導入。
6。最后一個謎題很重要哦,我現在才知道JDK5對條件操作符(a?b:c)已經有重大改變。試著分別在JDK1.4和JDK5中運行下面的程序:
import java.util.Random;
public class CoinSide {
private static Random rnd = new Random();
public static CoinSide flip() {
return rnd.nextBoolean() ?
Heads.INSTANCE : Tails.INSTANCE;
}
public static void main(String[] args) {
System.out.println(flip());
}
}
class Heads extends CoinSide {
private Heads() { }
public static final Heads INSTANCE = new Heads();
public String toString() {
return "heads";
}
}
class Tails extends CoinSide {
private Tails() { }
public static final Tails INSTANCE = new Tails();
public String toString() {
return "tails";
}
}
發(fā)現了嗎?在jdk1.4及以前版本當中,此程序無法通過,報錯:
incompatible types for ?: neither is a subtype of the other
說什么第2個操作數和第3個操作數都不是另外一個子類。而在JDK5下這個程序將正常運行,隨機打印heads或者tails。這是因為在JDK5以前,條件運算符要求:當第2個和第3個操作數是引用類型時,它們其中的一個必須是另外一個的子類。而例子中Heads和Tails都不是對方的子類,所以產生了上面的錯誤。而在JDK5中,這個條件放寬了,第2個和第3個操作數如果是引用那么都是合法的,只不過其結果類型將是這兩種類型的最小公共超類。此例中Heads和Tails的超類向上追溯有CoinSide,Object,而CoinSide是他們的最小公共超類。
如果想在JDK5以前運行上面的程序,可以把第2或者第3操作數向上轉型為他們的超類即可:
public static CoinSide flip() {
return rnd.nextBoolean() ?
(CoinSide)Heads.INSTANCE : Tails.INSTANCE;
}
另外一些謎題討論了對Object類中方法的覆寫問題,特別要注意不要覆寫變成了重載
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=728172