Effective Java笔记第三章类和接口
第五节要么为继承而设计,并提供文档说明,要么就禁止继承
1.对于专门为了继承而设计并具有良好文档说明的类而言,首先,该类的文档必须精确地描述覆盖每个方法所带来的影响。换句话说,该类必须有文档说明它可覆盖的方法的自用性。对于每个公有的或受保护的方法或者构造器,他的文档必须指明该方法或者构造器调用了哪些可覆盖的方法,是以什么顺序调用的,每个调用的结果又是如何影响后续的处理过程的(所谓可覆盖的方法,是指非final的,公有的或受保护的)。更一般地,类必须在文档中说明,在哪些情况下他会调用可覆盖的方法。
2.如果方法调用到了可覆盖的方法,在他的文档注释的末尾应该包含关于这些调用的描述信息。这段描述信息要以这样的句子开头:“This implementation(该实现…)”。这样的句子不应该被认为是在表明该行为可能会随着版本的变迁而改变。它意味着这段描述关注该方法的内部工作情况。
3.关于程序文档有句格言:好的API文档应该描述一个给定的方法做了什么工作,而不是描述他是如何做到的。
4.为了继承而进行的设计不仅仅涉及自用模式的文档设计。为了使程序员能够编写更加有效的子类,而无需承受不必要的痛苦,类必须通过某种形式提供适当的钩子,以便能够进入他的内部工作流程中,这种形式可以是精心选择的受保护的方法,也可以是受保护的域,但是后者比较少见。
5.对于为了继承而设计的类,唯一的测试方法就是编写子类。如果遗漏了关键的受保护成员,尝试编写子类就会使遗漏所带来的痛苦变得更加明显。相反,如果编写了多个子类,并且无一使用受保护的成员,或许就应该把它做成私有的。
6.在为了继承而设计有可能被广泛使用的类时,必须意识到,对于文档中所说明的自用模式,以及对于其受保护方法和域中所隐含的实现策略,你实际上已经做了永久的承诺。这些承诺使得你在后续的版本中提高这个类的性能或者增加新功能都变得非常困难,甚至不可能。因此,必须在发布类之前先编写子类对类进行测试。
7.为了允许继承,类还必须遵守其他一些约束。构造器决不能调用可被覆盖的方法,无论是直接调用还是间接调用。下面我们举个例子:
超类
public class Super {
public Super() {
overrideMe();
}
public void overrideMe(){}
}
子类
public class Sub extends Super {
private final Date date;
Sub( ) {
date=new Date();
}
@Override
public void overrideMe() {
System.out.println(date);
}
public static void main(String[] args) {
Sub sub=new Sub();
sub.overrideMe();
}
}
输出:
null
Fri Feb 19 19:25:27 CST 2021
会出现上面这种输出是因为继承首先调用超类的构造器,overrideMe被超类Super构造器调用的时候,子类构造器Sub还没机会初始化date域。所以第一次调用被覆盖的overrideMe方法时,date为null。子类构造器初始化后,子类调用overrideMe方法才输出时间。
8.如果你决定在一个为了继承而设计的类中实现Cloneable或者Serializable接口,就应该意识到,因为clone和readObject方法在行为上非常类似于构造器,所以类似的限制规则也是适用的:无论clone还是readObject,都不可以调用可覆盖的方法,不管是以直接的还是间接的方法。对于readObject方法,覆盖版本的方法将在子类的状态被反序列化之前先被运行;而对于clone方法,覆盖版本的方法则是在子类的clone方法有机会修正被克隆对象的状态之前先被运行。
9.如果你决定在一个为了继承而设计的类中实现Serializable,并且该类有一个readResolve或者writeReplace方法,就必须使readResolve或者writeReplace成为受保护的方法,而不是私有的方法。如果这些方法是私有的,那么子类将会不声不响的忽略掉这两个方法。这正是"为了允许继承,而把实现细节变成一个类的API的一部分"的另一种情形。
10.对于那些并非为了安全的进行子类化而设计和编写文档的类,要禁止子类化。对此一般有两种方法。
1)把这个类声明为final的。
2)把所有的构造器变成私有的,或者包级私有的,并增加一些公有的静态工厂来代替构造器。
11.如果类实现了某个能够反映其本质的接口,比如Set,List或者Map,就不应该为了禁止子类化而感到后悔。因为我们可以使用包装类来让继承机制实现更多的功能。
12.如果具体的类没有实现标准接口,你认为必须允许继承这样的类,一种合理的方法就是确保这个类永远不会调用它的任何可覆盖的方法,并在文档中说明这一点。换句话,完全消除这个类中可覆盖方法的自用特性。这样做之后,就可以创建"能够安全的进行子类化"的类。覆盖方法将永远也不会影响其他的任何方法的行为。
13.你可以机械的消除类中可覆盖方法的自用特性,而不改变他的行为。将每个可覆盖方法的代码体移到一个私有的"辅助方法"中,并且让每个可覆盖的方法调用它的私有辅助方法。然后,用"直接调用可覆盖方法的私有辅助方法"来代替"可覆盖方法的每个自用调用"。