类和接口-要么设计继承并提供文档说明,要么禁止继承

216 阅读3分钟

这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

前言

对于不是为了继承而设计并且没有文档说明的“外来”类而言进行子类化十分危险,如果要对那种为了继承而设计的且有良好文档说明的类而言,我们又该如何做呢?

对需要继承的类有何要求

文档必须说明可覆盖方法的自用性

对于专门为了继承而设计并且具有良好文档说明的类而言,该类的文档必须精确地描述覆盖每个方法所带来的影响。该类必须有文档说明它可覆盖的方法的自用性。对于每个公有的或受保护的方法或者构造器,它的文档必须指明该方法或者构造器调用了哪些可覆盖的方法,是以什么顺序调用的,每个调用的结果又是如何影响后续的处理过程的。
类必须在文档中说明,在哪些情况下它会调用可覆盖的方法。比如后台线程或者静态的初始类有没有调用了我们的可覆盖的方法。

发布类之前必须考虑好要提供给子类的钩子

如何决定暴露哪些受保护的方法和域呢?暴露的太少会导致子类根本无法被继承,暴露的太多又会导致子类有可能破坏我们的代码的方法,为了继承而设计的类,要想做到良好的测试,编写三个子类就可以了。这三个子类要来测试父类中暴露的方法是否足够,是否能够进行良好的扩展。

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

构造器决不能调用可被覆盖的方法,无论是直接调用还是间接调用。 这是因为超类的构造器在子类的构造器之前运行,所以,子类中覆盖版本的方法将会在子类的构造器运行之前就先被调用。如果该覆盖版本的方法信赖于子类构造器所执行的任何初始化操作,该方法将不会如预期执行;
比如下面的程序

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

    }
}

public final class Sub extends Super {
    private final Instant instant;
    Sub(){
        instant=Instant.now();
    }
    @Override
    public void overrideMe(){
        System.out.println(instant);
    }
    public static void main(String[] args){
        Sub sub =new Sub();
        sub.overrideMe();
    }
}

image.png 父类中的构造器调用时,子类的构造器并没有初始化instant,这时候直接报NullPointException,这里没报错是因为println可以忍受null参数。 还有clone和readObject方法也是如此。

非设计用来子类化的类,不能子类化

对于那些并非为了安全地进行子类化而设计和编写文档的类,要禁止子类化。有两种办法可以禁止子类化:第一,把这个类声名为final。第二、把所有的构造器都声名为private,或者包级私有的,并增加一些公有的静态工厂来替代构造器。