1. 程式人生 > >如何看破真假美猴王 ? --java中的Shadowing和Obscuring

如何看破真假美猴王 ? --java中的Shadowing和Obscuring

故事背景

《西遊記》第五十七回:唐僧因悟空又打死攔路強盜,再次把他攆走。六耳獼猴精趁機變作悟空模樣,搶走行李關文,又把小妖變作唐僧、八戒、沙僧模樣,欲上西天騙取真經。真假二悟空從天上殺到地下,菩薩、玉帝、地藏王等均不能辨認真假,直到雷音寺如來佛處,才被佛祖說出本相,獼猴精被悟空打死。

 

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