Java 中有时候也会出现真假美猴王的事件,请看下面的程序后打印什么?
publicclassPet { publicfinalStringname; publicfinalStringfood; publicfinalStringsound; publicPet(Stringname, Stringfood, Stringsound) { this.name=name; this.food=food; this.sound=sound; } publicvoideat() { System.out.println(name+": Mmmmm, "+food); } publicvoidplay() { System.out.println(name+": "+sound+" "+sound); } publicvoidsleep() { System.out.println(name+": Zzzzzzz..."); } publicvoidlive() { newThread() { publicvoidrun() { while (true) { eat(); play(); sleep(); } } }.start(); } publicstaticvoidmain(String[] args) { newPet("Fido", "beef", "Woof").live(); } }
我们期望程序打印:
Fido: Mmmmm, beefFido: WoofWoofFido: 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.*/publicstaticnativevoidsleep(longmillis) throwsInterruptedException; /*** 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.*/publicstaticvoidsleep(longmillis, intnanos) throwsInterruptedException { if (millis<0) { thrownewIllegalArgumentException("timeout value is negative"); } if (nanos<0||nanos>999999) { thrownewIllegalArgumentException( "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 举例:
classTest1 { publicstaticvoidmain(String[] args) { inti; for (inti=0; i<10; i++) System.out.println(i); } }
编译报错,但编译检测也不是万能的,也有一些 trick 来逃避:
classTest2 { publicstaticvoidmain(String[] args) { inti; classLocal { { for (inti=0; i<10; i++) System.out.println(i); } } newLocal(); } }
如果在不同 block,则不会出现 Shadowing 的问题:
classTest3 { publicstaticvoidmain(String[] args) { for (inti=0; i<10; i++) System.out.print(i+" "); for (inti=10; i>0; i--) System.out.print(i+" "); System.out.println(); } }
原因找到了,那该怎么解决呢?
问题解决
方式一:线程内调用,改成 Pet.this.sleep(); 限定具体的方法:
publicvoidlive() { newThread() { publicvoidrun() { while (true) { eat(); play(); Pet.this.sleep(); } } }.start(); }
方式二:将 sleep 名称改为其它不冲突的名称,如 petSleep,然后线程内调用该方法:
publicvoidpetSleep() { System.out.println(name+": Zzzzzzz..."); }
方式三:也是最好的方式,使用 Thread(Runnable) 构造器来替代对 Thread 的继承。那个匿名类不会再继承Thread.sleep 方法,故也不会有冲突了。
publicvoidlive(){ newThread(newRunnable(){ publicvoidrun(){ while(true){ eat(); play(); sleep(); } } }).start(); }
