謎題92:雙絞線
下面這個程序使用一個匿名類執行了一個並不自然的動作。它會打印出什麽呢?
public class Twisted {
private final String name;
Twisted(String name) {
this.name = name;
}
private String name() {
return name;
}
private void reproduce() {
new Twisted("reproduce") {
void printName() {
System.out.println(name());
}
}.printName();
}
public static void main(String[] args) {
new Twisted("main").reproduce();
}
}
根據一個膚淺的分析會判斷該程序不能通過編譯。reproduce方法中的匿名類試圖調用Twisted類中的私有方法name。一個類不能調用另一個類的私有方法,是嗎?如果你試圖編譯這個程序,你會發現它可以成功地通過編譯。在頂層的類型(top-level type)中,即本例中的Twisted類,所有的本地的、內部的、嵌套的和匿名的類都可以毫無限制地訪問彼此的成員[JLS 6.6.1]。這是一個歡樂的大家庭。
在了解了這些之後,你可能會希望程序打印出reproduce,因為它在new Twisted(“reproduce”)實例上調用了printName方法,這個實例將字符串”reproduce”傳給其超類的構造器使其存儲到它的name域中。printName方法調用name方法,name方法返回了name域的內容。但是如果你運行這個程序,你會發現它打印的是main。現在的問題是它為什麽會做出這樣的事情呢?
這種行為背後的原因是私有成員不會被繼承[JLS 8.2]。在這個程序中,name方法並沒有被繼承到reproduce方法中的匿名類中。所以,匿名類中對於printName方法的調用必須關聯到外圍(“main”)實例而不是當前(“reproduce”)實例。這就是含有正確名稱的方法的最小外圍範圍(enclosing scope)(謎題 71和79)。
這個程序違反了謎題90中的建議:在”reproduce”中的匿名類即是Twisted類的內部類又擴展了它。單獨這一點就足以使程序難以閱讀。再加上調用超類的私有方法的復雜度,這個程序就成了純粹的冗長的廢話。這個謎題可以用來強調謎題6中的教訓:如果你不能通過閱讀代碼來分辨程序會做什麽,那麽它很可能不會做你想讓它做的事。要盡量爭取程序的清晰。
謎題92:雙絞線