如何看破真假美猴王 ? –java中的Shadowing和Obscuring
- 2019 年 10 月 3 日
- 筆記
故事背景
《西遊記》第五十七回:唐僧因悟空又打死攔路強盜,再次把他攆走。六耳獼猴精趁機變作悟空模樣,搶走行李關文,又把小妖變作唐僧、八戒、沙僧模樣,欲上西天騙取真經。真假二悟空從天上殺到地下,菩薩、玉帝、地藏王等均不能辨認真假,直到雷音寺如來佛處,才被佛祖說出本相,獼猴精被悟空打死。
java之真假美猴王
java中有時候也會出現真假美猴王的事件,請看下面的程式後列印什麼?
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(); } }
我們期望程式列印:
Fido: Mmmmm, beef
Fido: Woof Woof
Fido: Zzzzzzz…
實際上報編譯錯誤。
The method sleep(long) in the type Thread is not applicable for the arguments ()
查看Thread的sleep方法:
/** * Causes the currently executing thread to sleep (temporarily cease * execution) for the specified number of milliseconds, subject to * the precision and accuracy of system timers and schedulers. The thread * does not lose ownership of any monitors. * * @param millis * the length of time to sleep in milliseconds * * @throws IllegalArgumentException * if the value of {@code millis} is negative * * @throws InterruptedException * if any thread has interrupted the current thread. The * <i>interrupted status</i> of the current thread is * cleared when this exception is thrown. */ public static native void sleep(long millis) throws InterruptedException; /** * Causes the currently executing thread to sleep (temporarily cease * execution) for the specified number of milliseconds plus the specified * number of nanoseconds, subject to the precision and accuracy of system * timers and schedulers. The thread does not lose ownership of any * monitors. * * @param millis * the length of time to sleep in milliseconds * * @param nanos * {@code 0-999999} additional nanoseconds to sleep * * @throws IllegalArgumentException * if the value of {@code millis} is negative, or the value of * {@code nanos} is not in the range {@code 0-999999} * * @throws InterruptedException * if any thread has interrupted the current thread. The * <i>interrupted status</i> of the current thread is * cleared when this exception is thrown. */ public static void sleep(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } sleep(millis); }
等等!
我不是要調用Thread的sleep方法,而是要調用Pet的sleep方法。為什麼出現這種情況呢?
JSL-6.4定義了這種情況:
It is a compile-time error if the name of a formal parameter is used to declare a new variable within the body of the method, constructor, or lambda expression, unless the new variable is declared within a class declaration contained by the method, constructor, or lambda expression. It is a compile-time error if the name of a local variable v is used to declare a new variable within the scope of v, unless the new variable is declared within a class whose declaration is within the scope of v. It is a compile-time error if the name of an exception parameter is used to declare a new variable within the Block of the catch clause, unless the new variable is declared within a class declaration contained by the Block of the catch clause. It is a compile-time error if the name of a local class C is used to declare a new local class within the scope of C, unless the new local class is declared within another class whose declaration is within the scope of C.
java中有Shadowing(遮蔽)的描述,其中:
Shadowing:Some declarations may be shadowed in part of their scope by another declaration of the same name, in which case a simple name cannot be used to refer to the declared entity.
簡單的意思是:在作用域內,一個地方的聲明可能被另一個同名的聲明所遮蔽。在這種情況下不能簡單的使用名字來引用他們所聲明的實體。
變數Shadowing舉例:
class Test1 { public static void main(String[] args) { int i; for (int i = 0; i < 10; i++) System.out.println(i); } }
編譯報錯,但編譯檢測也不是萬能的,也有一些trick來逃避:
class Test2 { public static void main(String[] args) { int i; class Local { { for (int i = 0; i < 10; i++) System.out.println(i); } } new Local(); } }
如果在不同block,則不會出現Shadowing的問題:
class Test3 { public static void main(String[] args) { for (int i = 0; i < 10; i++) System.out.print(i + " "); for (int i = 10; i > 0; i--) System.out.print(i + " "); System.out.println(); } }
原因找到了,那該怎麼解決呢?
問題解決
方式一:執行緒內調用,改成Pet.this.sleep();限定具體的方法:
public void live() { new Thread() { public void run() { while (true) { eat(); play(); Pet.this.sleep(); } } }.start(); }
方式二:
將sleep名稱改為其它不衝突的名稱,如petSleep,然後執行緒內調用該方法
public void petSleep() { System.out.println(name + ": Zzzzzzz..."); }
方式三:也是最好的方式,使用Thread(Runnable)構造器來替代對Thread 的繼承。那個匿名類不會再繼承Thread.sleep 方法,故也不會有衝突了。
public void live(){ new Thread(new Runnable(){ public void run(){ while(true){ eat(); play(); sleep(); } } }).start(); }
參考資料
【1】https://docs.oracle.com/javase/specs/jls/se12/html/jls-6.html#jls-6.4
【2】java解惑