下面的這個類模拟了一個家庭寵物的生活。main 方法建立了一個 Pet 執行個體,用
它來表示一隻名叫 Fido 的狗,然後讓它運作。雖然絕大部分的狗都在後院裡奔
跑(run),這隻狗卻是在背景運作(run)。那麼,這個程式會列印出什麼呢?
public class Pet{
public final String name;
public final String food;
public final String sound;
public Pet(String name, String food, String sound){
this.name = name;
this.food = food;
this.sound = sound;
}
public void eat(){
System.out.println(name + ": Mmmmm, " + food );
}
public void play(){
System.out.println(name + ": " + sound + " " + sound);
}
public void sleep(){
System.out.println(name + ": Zzzzzzz...");
}
public void live(){
new Thread(){
public void run(){
while(true){
eat();
play();
sleep();
}
}
}.start();
}
public static void main(String[] args){
new Pet("Fido", "beef", "Woof").live();
}
}
main 方法建立了一個用來表示 Fido 的 Pet 執行個體,并且調用了它的 live 方法。
然後,live 方法建立并且啟動了一個線程,該線程反複的調用其外圍
(enclosing)的 Pet 執行個體的 eat、play 和 sleep 方法,就這麼一直進行下去。
這些方法都會列印單獨的一行,是以你會想到這個程式會反複的列印以下的 3
行:
Fido: Mmmmm, beef
Fido: Woof Woof
Fido: Zzzzzzz…
但是如果你嘗試運作這個程式,你會發現它甚至不能通過編譯。而産生的編譯錯
誤資訊沒有什麼用處:
Pet.java:28: cannot find symbol
symbol: method sleep()
sleep();
為什麼編譯器找不到那個符号呢?這個符号确實是白紙黑字地寫在那裡。與謎題
74 一樣,這個問題的源自重載解析過程的細節。編譯器會在包含有正确名稱的
方法的最内層範圍内查找需要調用的方法[JLS 15.12.1]。在我們的程式中,對
于對 sleep 方法的調用,這個最内層的範圍就是包含有該調用的匿名類
(anonymous class),這個類繼承了 Thread.sleep(long)方法和
Thread.sleep(long,int)方法,它們是該範圍内唯一的名稱為 sleep 的方法,但
是由于它們都帶有參數,是以都不适用于這裡的調用。由于該方法調用的 2 個候
選方法都不适用,是以編譯器就列印出了錯誤資訊。
從 Thread 那裡繼承到匿名類中的 2 個 sleep 方法遮蔽(shadow)[JLS 6.3.1]
了我們想要調用的 sleep 方法。正如你在謎題 71 和謎題 73 中所看到的那樣,你
應該避免遮蔽。在這個謎題中的遮蔽是間接地無意識地發生的,這使得它更加
“陰險”。
訂正這個程式的一個比較顯而易見的方法,就是把 Pet 中的 sleep 方法的名字改
成 snooze, doze 或者 nap。訂正該程式的另一個方法,是在方法調用的時候使
public void live(){
new Thread(new Runnable(){
public void run(){
while(true){
eat();
play();
sleep();
}
}
}).start();
}
[ 尐魚兒的QQ群:726994578 ] --- [ https://github.com/godmaybelieve ]