Java更多的類謎題66:一件私事

字號:

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