《Effective Java》笔记17:要么为继承而设计,并提供文档说明,要么就禁止继承

  1. 构造器决不能调用可被覆盖的方法
  2. Cloneable 和 Serializable 接口
  3. 禁止子类化

所有为继承而设计的类都必须有文档来说明它可覆盖的方法的自用性。对于每个公有的或受保护的方法或者构造器,它的文档必须指明该方法或者构造器调用了哪些可覆盖的方法,是以什么顺序调用的,每个调用的结果以是如何影响后续的处理过程的。更一般地,类必须在文档中说明,在哪些情况下它会调用可覆盖方法。

类必须通过某种形式提供适当的钩子(hook),以便能够进行入到它的内部工作流程,这种形式可以是精心选择的受保护(protected)方法,也可以是受保护的域。例如, java.util.AbstractList 中的 protected void removeRange(int fromIndex, int toIndex) 方法。

构造器决不能调用可被覆盖的方法

为了允许继承,类还必须遵守其他一些约束。构造器决不能调用可被覆盖的方法。因为超类的构造器在子类的构造器之前运行,所以,子类中覆盖版本的方法将会在子类的构造器运行之前就先被调用,如果该覆盖版本的方法依赖于子类构造器所执行的任何初始化工作,该方法将不会如预期般地执行。

由于超类的初始化早于子类的初始化,如果此时调用的方法被子类覆盖,而覆盖的方法中又引用了子类中的域字段,这将很容易导致 NullPointerException 异常被抛出,见下例:

public class SuperClass {
    public SuperClass() {
        overrideMe();
    }
    public void overrideMe() {
    }
}

public final class SubClass extends SuperClass {
    private final Date d;
    SubClass() {
        d = new Date();
    }
    @Override
    public void overrideMe() {
        System.out.println(dd.getDay());
    }
}

public static void main(String[] args) {
    SubClass sub = new SubClass();
    sub.overrideMe();
}

Cloneable 和 Serializable 接口

如果你决定在一个为了继承而设计的类中实现 Cloneable 或者 Serializable 接口,就应该意识到,因为 clone 和 readObject 方法在行为上非常类似于构造器,所以类似的限制规则也是适用的:无论是 clone 还是 readObject,都不可以调用可覆盖的方法。对于 readObject 方法,覆盖版本的方法将在子类的状态被反序列化前先被运行;而对于 clone 方法,覆盖版本的方法则在子类的 clone 方法调用来修正被克隆对象的状态之前先被运行(即父类的 clone 方法调用了覆盖的方法,而子类的 clone 却还没调用,这样就可能没有初始化这个覆盖方法所依赖的数据)。

如果你决定在一个为了继承而设计的类中实现 Serializable 接口,并且该类有一个 readResolve(Singleton 序列化时需要使用) 或者 writeReplace 方法,就必须使这两个方法成为被保护的方法,而不是私有的方法。

禁止子类化

对于普通的具体类应该怎么办?他们即不是 final 的,也不是为了子类化而设计和编写文档的,所以这种状况很危险。这个问题的最佳解决方案是,对于那些并不能安全进行子类化又没有编写文档的类,要禁止子类化。禁止子类化有两种方法,一种就是定义成 final 的,另一种就是私有构造器,交提供静态工厂方法,可参考第15条。

这条建议可能会引来争议,因为许多程序员已经习惯了对普通的具体类进行子类化,以便增加新的功能。如果这个普通类实现了某个接口,比如 Set、List 或者 Map,就完全可以禁止该普通类可子类化,因为即使禁止了,我们可以采第 16 条里的组合与转发即包装模式来提供一种更好的办法;如果具休的类并没有实现标准的接口,那么禁止继承可能会给有些程序员带来不便,如果认为必须允许从这样的类继承,一种合理的办法是确保这个类永远不会自己调用自已的任何可覆盖的方法,并在文档中说明这一点,换句话说,完全消除这个类中可覆盖方法的自用特性,这样做后就可以创建“能够安全地进行子类化”的类。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com

文章标题:《Effective Java》笔记17:要么为继承而设计,并提供文档说明,要么就禁止继承

文章字数:1.1k

本文作者:Bin

发布时间:2018-10-11, 21:06:22

最后更新:2019-08-06, 00:43:11

原始链接:http://coolview.github.io/2018/10/11/Effective-Java/%E3%80%8AEffective%20Java%E3%80%8B%E7%AC%94%E8%AE%B017%EF%BC%9A%E8%A6%81%E4%B9%88%E4%B8%BA%E7%BB%A7%E6%89%BF%E8%80%8C%E8%AE%BE%E8%AE%A1%EF%BC%8C%E5%B9%B6%E6%8F%90%E4%BE%9B%E6%96%87%E6%A1%A3%E8%AF%B4%E6%98%8E%EF%BC%8C%E8%A6%81%E4%B9%88%E5%B0%B1%E7%A6%81%E6%AD%A2%E7%BB%A7%E6%89%BF/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录