类要么被设计成专用于继承(并辅以清楚的文档),要么就完全禁止继承
item 16提到过,继承一个本软件包以外的类是危险的,除非它被设计成专用于继承。这节就是讲一个"专用于继承的类"该是什么样子。
首先,这个类必须用文档注明它自己对"可覆盖方法"的使用情况(所谓"可覆盖方法",就是非final的、并且是public或protected的方法)。比如:某个方法调用了哪些可覆盖方法,m88体育 调用顺序怎样,m88体育 的返回值对后续处理将产生怎样的影响。还应注明在哪些情况下,可覆盖方法会被调用。比如调用者可能是后台运行的其他线程或静态初始化代码。
作为一种通用约定,如果某个方法调用了可覆盖方法,那么在它文档注释的末尾,要加上对这些调用的描述。这些描述需要以"This implementation"开头,比如下面的java.util.AbstractCollection类中的注释:
/** * Removes a single instance of the specified element * from this collection, if it is present (optional operation). * More formally, removes an element e such that * (o==null ? e==null : o.equals(e)), if the collection contains * one or more such elements. Returns true if the collection * contained the specified element (or equivalently, if the * collection changed as a result of the call). * This implementation iterates over the collection looking * for the specified element. If it finds the element, it removes * the element from the collection using the iterator’s remove * method. Note that this implementation throws an * UnsupportedOperationException if the iterator returned by * this collection’s iterator method does not implement the * remove method. */ public boolean remove(Object o) { ... }
这段注释说明了remove方法的行为依赖于iterator方法返回的结果,所以如果开发者需要覆盖iterator方法,则必须预见到对remove方法的影响。
设计可继承类,除了对可覆盖方法的内部调用做文档注释以外,还经常需要以protected方法或属性的形式,提供一些可改变内部行为的"钩子"(hook)。比如java.util.AbstractList类中的removeRange方法:
/** * Removes from this list all of the elements whose index * is between fromIndex, inclusive, and toIndex, exclusive. * Shifts any succeeding elements to the left (reduces their * index). This call shortens the ArrayList by * (toIndex - fromIndex) elements. (If toIndex == fromIndex, * this operation has no effect.) * This method is called by the clear operation on this list * and its sublists. Overriding this method to take advantage * of the internals of the list implementation can substantially * improve the performance of the clear operation on this list * and its sublists. * This implementation gets a list iterator positioned before * fromIndex and repeatedly calls ListIterator.next followed * by ListIterator.remove, until the entire range has been * removed. Note: If ListIterator.remove requires linear time, * this implementation requires quadratic time. * Parameters: * fromIndex index of first element to be removed. * toIndex index after last element to be removed. */ protected void removeRange(int fromIndex, int toIndex) { ... }
这一方法对于List实现的最终用户来说没什么用处。它唯一的用途是为子类提供一个方法,来快速清空List中的某一段。
在API发布之前,可继承类应接受测试。而唯一的测试方法,就是创建子类来继承它。需要有至少3个子类来确保设计合理,至少其中两个由父类开发者以外的人来创建。
关于可继承类,还有一个重要的限制:构造函数不能调用任何可被覆盖的方法,直接和间接都不行。这是因为父类构造函数会先于子类构造函数运行,所以被子类覆盖的方法也会先于子类构造函数被(父类构造函数)调用。如果这样的方法依赖于任何子类的初始化代码,它们将无法正确运行。下面是一个例子:
public class Super { // Broken - constructor invokes an overridable method public Super() { overrideMe(); } public void overrideMe() { } } public final class Sub extends Super { private final Date date; // Blank final, set by constructor Sub() { date = new Date(); } // Overriding method invoked by superclass constructor @Override public void overrideMe() { System.out.println(date); } public static void main(String[] args) { Sub sub = new Sub(); sub.overrideMe(); } }
执行上面的代码将输出null和当前日期,而不是预期的两次输出当前日期。这正是因为,当父类构造函数调用overrideMe方法时,子类构造函数尚未运行,因而date属性还没有被赋值。
与构造函数类似,实现了Cloneable和Serializable接口的类也应禁止clone和readObject方法对可覆盖方法的调用。
由于有以上这些限制,对于继承架构的采用,必须谨慎。只在一些真正适用的场景,比如抽象类或者骨架实现(skeletal implementation),才能使用。在另一些场景中,采用继承架构则显然是错误的,比如对于不变类(item 15)。对于其它普通的类,也应尽可能关闭类的可继承性,而采用wrapper设计模式(item 16)。