名字重用:
覆写(override):一个实例方法可以overide在其超类中可以访问到的具有相同签名的所有方法,从而可以动态分派(dynamic dispatch)
VM基于运行时类型来选择要调用的覆写方法。
class Base {
public void f() {
}
}
class Derived extends Base {
public void f() {
} // overrides Base.f()
}
隐藏 hide
一个域,静态方法,或成员类型可以分别被隐藏(hide)在其超类可以访问到的具有相同名字的所有域、静态方法或成员类型。hide几个成员将阻止其被继承。JLS 8.3,8.4,8.5
package arkblue.lang.javapuzzler.n75.hide;
class Base {
public static void f() {
}
}
class Derived extends Base {
private static void f() {
} // hides Base.f()
}
上例将不能编译通过:Cannot reduce the visibility of the inherited method from Base。
修改为
package arkblue.lang.javapuzzler.n75.hide;
class Base {
static int a = 1;
int b = 3;
public static void f() {
System.out.println("Base");
}
}
class Derived extends Base {
static int a = 2;
int b = 4;
public static void f() {
System.out.println("Derived");
} // hides Base.f()
}
public class Hide {
public static void main(String[] args) {
Base.f();
Derived.f();
System.out.println(Base.a);
System.out.println(Derived.a);
System.out.println((new Base()).b);
System.out.println((new Derived()).b);
System.out.println(((Base) (new Derived())).b);
}
}
打印结果
Base
Derived
1
2
3
4
3
重载(overload)
在某个类中的方法可以重载(overload)另一个方法,只要它们具有相同的名字和不同的签名。由调用所指定的重载方法是在编译期选定的[JLS 8.4.9, 15.12.2]:
package arkblue.lang.javapuzzler.n75.override;
class CircuitBreaker {
public void f(int i) {
} // int overloading
public void f(String s) {
} // String overloading
}
遮蔽(shadow)
一个变量、方法或类型可以分别遮蔽(shadow)在一个闭合的文本范围内的具有相同名字的所有变量、方法或类型。如果一个实体被遮蔽了,那么你用它的简单名是无法引用到它的;根据实体的不同,有时你根本就无法引用到它[JLS 6.3.1]:
例子
package arkblue.lang.javapuzzler.n75.shadow;
class WhoKnows {
static String sentence = "I don't know.";
public static void main(String[] args) {
String sentence = "I know!"; // shadows static field
System.out.println(sentence); // prints local variable
}
}
遮掩(obscure)一个变量可以遮掩具有相同名字的一个类型,只要它们都在同一个范围内:如果这个名字被
用于变量与类型都被许可的范围,那么它将引用到变量上。相似地,一个变量或一个类型可以遮掩一个包。遮掩是唯一一种两个名字位于不同的名字空间的名字重用形式,这些名字空间包括:变量、包、方法或类型。
例如下面的代码不能编译:变量遮掩了类型。
package arkblue.lang.javapuzzler.n75.obscure;
public class Obscure {
static String System; // Obscures type java.lang.System
public static void main(String[ ] args) {
// Next line won't compile: System refers to static field
System.out.println(”hello, obscure world!“);
}
}
---------------------------------------------------------------------------------------------------------------
1
package arkblue.lang.initialclass.jicheng;
class BaseClass {
int a = 3;
static int b = 5;
void test() {
System.out.println("BaseClass");
}
static void test1() {
System.out.println("BaseClass static method");
}
}
public class ExtendsTest3 extends BaseClass {
public int a = 4;
static int b = 6;
void test() {
System.out.println("ExtendsTest3");
}
static void test1() {
System.out.println("ExtendsTest3 static method");
}
public static void main(String[] args) {
BaseClass test = new ExtendsTest3();
System.out.println(test.a); // 父类的实例变量
System.out.println(test.b); // 父类的静态变量
test.test(); // 子类对象的方法
test.test1(); // 父类的方法
ExtendsTest3 test1 = new ExtendsTest3();
System.out.println(test1.a); // 子类实例变量
System.out.println(test1.b); // 子类静态变量
test1.test(); // 子类对象的方法
test1.test1(); // 子类方法
}
}
输出结果
3
5
ExtendsTest3
BaseClass static method
4
6
ExtendsTest3
ExtendsTest3 static method
对静态方法的调用不存在动态分派的机制,不要用一个表达式来标示一个静态方法的调用,应该使用类来标识。 当程序调用静态方法时,被调用的方法是在编译时刻被选定的,基于修饰符的编译期类型而作出的,修饰符的编译期类型就是我们给出的方法调用表达式圆点左边部分的名字。
2 令人混淆的构造器
package arkblue.lang.initialclass.jicheng;
/**
* (1)java的重载解析过程分两个阶段,第一阶段选取所有可获得并且可应用的方法或构造器;
* 第二个阶段从第一阶段的结果中选择最精确的一个。
* (2)应当避免使用重载,为不同的方法取不同的名字;将构造器设置为私有并提供静态工厂方法;如果有多个参数,可使用Builer模式
* @author ark
*
*/
public class ChongzaiTest {
public ChongzaiTest(Object obj) {
System.out.println("ChongzaiTest(Object obj)");
}
public ChongzaiTest(Double doub) {
System.out.println("ChongzaiTest(Double doub)");
}
// 打开注释,则new ChongzaiTest(null); 则不能通过编译
// public ChongzaiTest(Integer doub) {
// System.out.println("ChongzaiTest(Integer doub)");
// }
public ChongzaiTest(double doub) {
System.out.println("ChongzaiTest(double doub)");
}
public static void main(String[] args) {
new ChongzaiTest(null);
new ChongzaiTest((Object) null);
new ChongzaiTest((double)1L);
new ChongzaiTest(123);
new ChongzaiTest(new Integer(123));
new ChongzaiTest(new Double(123L));
}
}
ChongzaiTest(Double doub)
ChongzaiTest(Object obj)
ChongzaiTest(double doub)
ChongzaiTest(double doub)
ChongzaiTest(Object obj)
ChongzaiTest(Double doub)
3 java Puzzlers 66 :子类的一个域具有和超类的一个域相同的名字。
(1)override一个复写的方法的访问修饰符搜提供的访问权限与被覆写方法的访问修饰符所提供的权限相比,至少一样多。JLS 8.4.8.3
(2)一个子类的域和父类有同样的名字,则会隐藏hide父类的同名域,如果子类隐藏域的访问权限比父类被隐藏域的访问权限小,也是合法的,此时子类的与如果和父类被隐藏的域的类型不一致,子类的定义也是合法的。
package arkblue.lang.javapuzzler.n66;
class Base {
public String className = "Base";
}
class Derived extends Base {
private String className = "Derived";
}
public class PrivateMatter {
public static void main(String[] args) {
System.out.println(new Derived().className);
}
}
这个例子将会抛出异常
The field Derived.className is not visible
4 java puzzler 68
当一个变量和一个类型具有相同的名字,并且他们位于相同的作用域时,变量名具有优先权 JLS 6.5.2,变量将遮掩(obscure)类型名。相似地,变量名和类型名可以遮掩包名。
例子
package arkblue.lang.javapuzzler.n68;
public class ShadowOfGray {
public static void main(String[] args) {
System.out.println(X.Y.Z);
}
}
class X {
static class Y {
static String Z = "Black";
}
static C Y = new C();
}
class C {
String Z = "White";
}
打印结果
White
如果要打印X.Y类中的Z域值 java puzzler 69,有下面几种方法
(1)我们可以引用一个被遮掩的类型名,在特殊的语法环境中允许出现一个类型但是不允许出现一个变量。
public class ShadowOfGray {
public static void main(String[] args) {
System.out.println(((X.Y)null).Z);
}
}
(2)在extends子句中使用一个被遮掩的类。
public class FadeToBlack {
static class Xy extends X.Y {
}
public static void main(String[] args) {
System.out.println(Xy.Z);
}
}
(3)在一个类型变量声明的extends子句中使用X.Y这种形式。
public class ShadowOfGray {
public static <T extends X.Y> void main(String[] args) {
System.out.println(T.Z);
}
}
5 java puzzler 70
一个包内私有的方法不能被位于另一个包中的某个方法直接复写(voerride)。
例子
arkblue.lang.javapuzzler.n70.click.CodeTalk
package arkblue.lang.javapuzzler.n70.click;
public class CodeTalk {
public void doIt() {
printMessage();
}
void printMessage() {
System.out.println("Click");
}
}
arkblue.lang.javapuzzler.n70.hack.TypeIt
package arkblue.lang.javapuzzler.n70.hack;
import arkblue.lang.javapuzzler.n70.click.CodeTalk;
public class TypeIt {
private static class ClickIt extends CodeTalk {
void printMessage() {
System.out.println("Hack");
}
}
public static void main(String[] args) {
ClickIt clickit = new ClickIt();
clickit.doIt();
}
}
运行main打印结果
Click
要想执行子类的printMessage方法,必须在父类的printMessage方法前添加访问修饰符protected,或者public。
并且修改子类的printMessage方法的访问修饰符,子类的访问修饰符的可见性不小于父类。
arkblue.lang.javapuzzler.n70.hack.TypeIt
package arkblue.lang.javapuzzler.n70.hack;
import arkblue.lang.javapuzzler.n70.click.CodeTalk;
public class TypeIt {
private static class ClickIt extends CodeTalk {
protected void printMessage() {
System.out.println("Hack");
}
}
public static void main(String[] args) {
ClickIt clickit = new ClickIt();
clickit.doIt();
}
}
和arkblue.lang.javapuzzler.n70.click.CodeTalk
package arkblue.lang.javapuzzler.n70.click;
public class CodeTalk {
public void doIt() {
printMessage();
}
protected void printMessage() {
System.out.println("Click");
}
}
6 java puzzler71 遮蔽(shadow)
(1)编译器在选择在运行期被调用的方法时,所做的第一件事就是在肯定能找到该方法的范围内挑选。JLS 15.12.1
编译器将在包含具有恰当名字的方法的最小闭合范围内挑选,下例中就是ImportDuty类,它包含了从Object继承的方法toString.
(2)导入的toString方法被继承的toString方法所遮蔽(shadow)
(3)本身就属于某个范围的成员在该范围内与静态导入相比具有优先权。
下面的代码不能编译通过
package arkblue.lang.javapuzzler.n70;
import static java.util.Arrays.toString;
class ImportDuty {
public static void main(String[] args) {
printArgs(1, 2, 3, 4, 5);
}
static void printArgs(Object... args) {
System.out.println(toString(args));
}
}
提示 The method toString() in the type Object is not applicable for the arguments (Object[])
7 java puzzler73 编写程序,原来客户端和库都可以正常编译,但是修改了库以后,库可以编译但是客户端不能编译。
库代码
arkblue.lang.javapuzzler.n73.library.Api
package arkblue.lang.javapuzzler.n73.library;
public final class Api {
// private static class String{ }
public static String newString() {
return new String();
}
}
客户端代码:
package arkblue.lang.javapuzzler.n73.client;
import arkblue.lang.javapuzzler.n73.library.Api;
public class Client {
String s = Api.newString();
}
如果打开Api 中的注释代码,则类中定义的String,shadow了引入的java.lang.String类型,并且是私有的,会导致Client编译失败。
使用hide(对静态域而言)可以达到同样效果
库代码
package arkblue.lang.javapuzzler.n73_1.library;
class ApiBase {
public static final int ANSWER = 42;
}
final class Api extends ApiBase {
// private static final int ANSWER = 3*4;
}
客户端代码
package arkblue.lang.javapuzzler.n73_1.library;
public class Client {
int answer = Api.ANSWER;
}
8 java puzzler 74 强烈建议:如果同一个方法的两个重载版本都可以应用于某些参数,那么它们应该具有相同的行为。
问题:编写Enigma 类的声明,使下面代码的执行打印 false
package arkblue.lang.javapuzzler.n74;
public class Conundrum {
public static void main(String[] args) {
Enigma e = new Enigma();
System.out.println(e.equals(e));
}
}
可以这样定义,重载了equals方法,但是不建议这样做。
final class Enigma {
// Don’t do this!
public Boolean equals(Enigma other) {
return false;
}
}
还有一种方法
final class Enigma {
public Enigma() {
System.out.println(false);
System.exit(0);
}
}
9 shadow的例 java puzzler 79
编译器会在包含有正确名称的方法的最内层范围内查找需要调用的方法[JLS 15.12.1
package arkblue.lang.javapuzzler.n79;
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(); //Thread.sleep方法 shadows了Pet的sleep方法
}
}
}.start();
}
public static void main(String[] args) {
new Pet("Fido", "beef", "Woof").live();
}
}