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.
注:如果形參的名字用在方法體、構造器體或者 lambda 表示式體內聲明的新變量,將會拋出編譯時錯誤,除非該新變量是在方法體、構造器體或者 lambda 表示式所包含的類聲明內聲明的。
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.
注:如果局部變量v的名字用來在v的作用域內聲明新的變量,將會拋出編譯時錯誤,除非該新變量是其類聲明在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.
注:如果表達式參數的名字用在catch子句的語句塊內聲明的新變量,將會拋出編譯時錯誤,除非該新變量是在catch子句的語句塊所包含的類聲明內聲明的。
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.
注:如果局部類c的名字用來在c的作用域內聲明新的局部類,將會拋出編譯時錯誤,除非該新局部類是在其類聲明在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();
}