继承中的同名字段
问题:
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);
}
}
报错
解析:父类 public
类型的字段被子类隐藏了(即父子类具有相同名字的字段,但是子类的访问控制权限要小于父类的),可以通过转型访问到父类的字段。要避免子类隐藏父类字段。 而对于实例方法,子类必须提供不小于父类的方法访问权限。
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(((Base)new Derived()).className);
}
}
使用类库中类的名字
问题:
public class StrungOut {
public static void main(String[] args) {
String s = new String("Hello world");
System.out.println(s);
}
}
class String {
private final java.lang.String s;
public String(java.lang.String s) {
this.s = s;
}
public java.lang.String toString() {
return s;
}
}
报错:找不到主方法
解析:因为主方法的参数定义为自定义的 String,而不是 java.lang.String。
总结:避免重用类名,尤其是类库中的类名。
命名问题
问题:
public class ShadesOfGray {
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
和 C
类,不使用反射,访问到 X.Y
类中的 Z
字段。
解决方案:寻找一个上下文环境,在这个环境下只允许出现一个类型而不允许出现一个变量…
// 1.
public class FadeToBlack {
public static void main(String[] args) {
// 这里 X.Y 只能引用类 Y 而不能引用变量 Y
System.out.println(((X.Y)null).Z);
}
}
// 2.
public class FadeToBlack {
// 这里 X.Y 只能引用类 Y 而不能引用变量 Y
static class Xy extends X.Y {}
public static void main(String[] args) {
System.out.println(Xy.Z);
}
}
// 3.
public class FadeToBlack {
// 这里 X.Y 只能引用类 Y 而不能引用变量 Y
public static <T extends X.Y> void main(String[] args) {
System.out.println(T.Z);
}
}
跨包访问
问题:
package click;
public class CodeTalk {
public void doIt() {
printMessage();
}
void printMessage() {
System.out.println("Click");
}
}
package hack;
import 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();
}
}
输出:
Click
解析:因为父类 CodeTalk 中的 printMessage()
方法没有访问权限修饰符,默认是包级访问权限,所以 TypeIt 并没有重写该方法。
解决方案:修改 printMessage()
方法的访问权限。
方法作用域
问题:
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));
}
}
编译错误
解析:编译器在选择在运行期被调用的方法时,会在能找到该方法的作用域内寻找,所以首先找到的是 ImportDuty 类继承自 Object 的 toString()
方法。参数不匹配,所以编译报错。本身就属于某个作用域的成员,在该作用域内,要比外部静态导入的有更高的优先权。所以最好还是用类名限定。
字段隐藏
问题:
class Jeopardy {
public static final String PRIZE = "$64,000";
}
public class DoubleJeopardy extends Jeopardy {
public static final String PRIZE = "2 cents";
public static void main(String[] args) {
System.out.println(DoubleJeopardy.PRIZE);
}
}
输出:
2 cents
解析: final
用于字段的时候,仅表示该字段不可更改(而用于类或方法时也表示不可重写和继承),所以子类的 PRIZE
字段将父类的隐藏了。
私有成员
问题:编写 final
的类库和客户类,都可以正常编译。然后在类库中添加一个私有成员,使得类库仍可以编译,但是客户类不能编译了。
解决方案:
// API
package library;
// 2.
class ApiBase {
public static final int ANSWER = 1;
}
// 2.
// public final class Api extends ApiBase{
// 1.
public final class Api {
// 1. 类型偷梁换柱
// private static class String {}
public static String newString() {
return new String();
}
// 2. 隐藏父类 public 字段。
// private static final int ANSWER = 2;
}
// Client
package client;
import library.Api;
public class Client {
// 1.
String s = Api.newString();
// 2.
int answer = Api.ANSWER;
}
同一性
问题:给出 Enigma 类定义,不能重写 equals()
,使得主函数输出 false
。
public class Conundrum {
public static void main(String[] args) {
Enigma e = new Enigma();
System.out.println(e.equals(e));
}
}
final class Enigma {
// Provide a class body that makes Conundrum print false.
// Do *not* override equals.
// 1. 重载 equals
public boolean equals(Enigma e) {
return false;
}
// 2. 投机取巧
public Enigma() {
System.out.println(false);
System.exit(0);
}
}
解决方案:1. 重载…(但是重载函数的行为应该得是一致的。) 2. 投机取巧…
名字重用术语表
- 重写(override)
- 重载(overload)
- 隐藏(hide)
一个字段、静态方法或成员类型可以分别隐藏在其超类中可访问道德具有相同名字的所有字段、静态方法或成员类型。隐藏一个成员将阻止其被继承。
class Base {
public static void f() {}
}
class Derived extends Base {
public static void f() {}// 隐藏父类 f()
}
- 遮蔽(shadow)
一个变量、方法或类型可以分别遮蔽在一个闭合作用域内具有相同名字的所有变量、方法和类型。
class WhoKnows {
static String sentence = "I don't know.";
public static void main(String[] args) {
String sentence = "I know!";
System.out.println(sentence);// I know!
}
}
构造器中关于遮蔽的通用法:
class Belt {
private final int size;
public Belt(int size) {
this.size = size;
}
}
- 遮掩(obscure)
一个变量可以遮掩具有相同名字的一个类型,只要它们在同一个作用域内。一个变量或一个类型可以遮蔽一个包,遮蔽是唯一一种两个名字位于不同的名字空间的名字重用形式。
public class Obscure {
static String System;
public static void main(String[] args) {
// 编译错误
System.out.println("Obscure");
}
}