第19条:若要设计继承,则提供文档说明,否则禁止继承
Item 18 alerted you to the dangers of subclassing a “foreign” class that was not designed and documented for inheritance. So what does it mean for a class to be designed and documented for inheritance?
条目18提醒我们,对于并不是为了继承而设计同时又没有良好的说明文档的“外来”类,去子类化它是很危险的。那么,“为了继承而设计同时提供文档说明”到底意味着什么呢?
First, the class must document precisely the effects of overriding any method. In other words, the class must document its self-use of overridable methods. For each public or protected method, the documentation must indicate which overridable methods the method invokes, in what sequence, and how the results of each invocation affect subsequent processing. (By overridable, we mean nonfinal and either public or protected.) More generally, a class must document any circumstances under which it might invoke an overridable method. For example, invocations might come from background threads or static initializers.
首先,必须在这个类的文档里为每个方法说明覆盖带来的影响。换句话说,必须在这个类的文档里为可覆盖方法说明它的自用性(self-use)。对于每个公有方法或受保护方法,文档里都必须指明这个方法调用了哪些可覆盖方法,是以什么顺序调用的,每个调用的结果是如何影响接下来的处理过程。(可覆盖是指非final,公有的或受保护的。)说得更通俗点,一个类的文档必须说明在哪些情况下它会调用可覆盖方法。例如,调用可能来自后台线程或者静态初始器(static initializer)。
A method that invokes overridable methods contains a description of these invocations at the end of its documentation comment. The description is in a special section of the specification, labeled “Implementation Requirements,” which is generated by the Javadoc tag @implSpec. This section describes the inner workings of the method. Here’s an example, copied from the specification for java.util.AbstractCollection:
调用可覆盖方法的方法应当在文档注释的末尾包含这些调用的描述。这个描述是规范中的特定部分,标记为“Implementation Requirements”,这由Javadoc标签@implSpec生成。这部分描述了方法的内部运作过程。下面举个例子,拷贝自java.util.AbstractCollection的规范:
public boolean remove(Object o)
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 Objects.equals(o, e), if this collection contains one or more such elements. Returns true if this collection contained the specified element (or equivalently, if this collection changed as a result of the call).
Implementation Requirements: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 and this collection contains the specified object.
如果集合里存在指定元素对象,则将其从这个集合里移除(可选操作)。正式地说,如果这个集合包含一个或多个这样的元素,则将满足Objects.equals(o, e)的元素移除。如果这个集合包含指定的元素,则返回true(如果因调用而使集合改变了,也一样返回true)。
实现要求:这个实现通过遍历集合来查找指定的元素。如果它找到这个元素,则使用迭代器的移除方法(remove method)将其从集合中移除。注意,如果集合的迭代器方法(iterator method)返回的迭代器没有实现移除方法同时集合也包含指定的对象的话,则这个将抛出UnsupportedOperationException异常。
This documentation leaves no doubt that overriding the iterator method will affect the behavior of the remove method. It also describes exactly how the behavior of the Iterator returned by the iterator method will affect the behavior of the remove method. Contrast this to the situation in Item 18, where the programmer subclassing HashSet simply could not say whether overriding the add method would affect the behavior of the addAll method.
文档明确说明了覆盖迭代方法将会影响移除方法的行为。文档还明确描述了迭代方法返回的迭代器的行为会影响移除方法的行为。与条目18不同,在条目18里提到的案例中,程序员继承HashSet时不能说明覆盖了add方法是否会影响addAll方法的行为。
But doesn’t this violate the dictum that good API documentation should describe what a given method does and not how it does it? Yes, it does! This is an unfortunate consequence of the fact that inheritance violates encapsulation. To document a class so that it can be safely subclassed, you must describe implementation details that should otherwise be left unspecified.
在编程界里有条名言:好的API文档应该是在描述方法是干什么的,而不是在说明它是如何做到的。这么说的话,那上面的文档岂不是违反了这条名言?是的,的确是。违反了封装是继承的一个无可奈何的副作用。为了让一个类可以被安全地继承,我们必须在文档里描述那些没有详细说明的实现细节。
The @implSpec tag was added in Java 8 and used heavily in Java 9. This tag should be enabled by default, but as of Java 9, the Javadoc utility still ignores it unless you pass the command line switch -tag "apiNote:a:API Note:".
@implSpec标签在Java 8里就已经被添加了,并且在Java 9得到大量使用。这个标签应该启用,但在Java 9中,Javadoc工具仍然忽视了它,除非你在命令行里输入开关命令:-tag"apiNot:a:API Note"。
Designing for inheritance involves more than just documenting patterns of self-use. To allow programmers to write efficient subclasses without undue pain, a class may have to provide hooks into its internal workings in the form of judiciously chosen protected methods or, in rare instances, protected fields. For example, consider the removeRange method from java.util.AbstractList:
继承的设计不仅仅只包含关于自用的文档。为了让程序员能高效的写出有效的子类,父类必须以某种形式提供能够进入到其内部运转的钩子(hook),这种形式可以是精心选择的受保护方法,或者是受保护域,只是后者比较少见。例如,考虑java.util.AbstractList的removeRange方法:
protected void removeRange(int fromIndex, int toIndex)
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 list 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.Implementation Requirements: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.
将fromIndex到toIndex范围内的元素从列表里移除,其中包括fromIndex,不包括toIndex。后续元素往左移(元素索引相应减小)。调用这个方法将从列表里减少(toIndex-fromIndex)个元素。(如果toIndex == fromIndex,这个操作将不起作用。)
这个方法被列表和子列表的clear操作调用。通过覆盖这个方法来利用列表实现的内部信息,可以大幅提升列表和子列表的clear操作的性能。
实现要求:这个实现获取了列表的一个迭代器,这个迭代器从fromIndex的前一个位置开始,递归地调用ListIterator.next方法,同时接着调用ListIterator.remove方法,直到整个范围的元素都被移除。注意:如果ListIterator.remove方法的执行需要花费线性级时间,那么该实现将花费平方级时间。
参数:
fromIndex 待移除的第一个元素的索引。
toIndex 待移除的最后一个元素的后一个索引。
This method is of no interest to end users of a List implementation. It is provided solely to make it easy for subclasses to provide a fast clear method on sublists. In the absence of the removeRange method, subclasses would have to make do with quadratic performance when the clear method was invoked on sublists or rewrite the entire subList mechanism from scratch—not an easy task!
这个方法对于使用List实现的终端用户来说是没有意义的。它仅仅是为了便于让子类在子列表里提供一个快速的clear方法。如果没有removeRange方法,那么在调用子列表的clear方法时,子类将不得不用平方级的时间,或者重新编写整个子列表的机制,这可不是件容易的事。
So how do you decide what protected members to expose when you design a class for inheritance? Unfortunately, there is no magic bullet. The best you can do is to think hard, take your best guess, and then test it by writing subclasses. You should expose as few protected members as possible because each one represents a commitment to an implementation detail. On the other hand, you must not expose too few because a missing protected member can render a class practically unusable for inheritance.
所以在设计一个被继承的类时我们应该如何决定应该暴露哪个受保护成员呢?遗憾的是,并没有什么好的可循方式。我们所能做的就是努力思考,多想想,然后编写一些子类进行测试。我们应该尽可能地少暴露受保护成员,因为每个受保护成员都表示着一个实现细节的承诺。另一方面,我们也不能暴露得太少,因为有时缺少某个受保护成员会导致类几乎不能被继承。
The only way to test a class designed for inheritance is to write subclasses. If you omit a crucial protected member, trying to write a subclass will make the omission painfully obvious. Conversely, if several subclasses are written and none uses a protected member, you should probably make it private. Experience shows that three subclasses are usually sufficient to test an extendable class. One or more of these subclasses should be written by someone other than the superclass author.
测试一个用于被继承的类的唯一方式是编写子类。如果你忽略了某个关键的受保护成员,那么当你尝试着写一个子类时,这个因忽略而导致的问题就会被明显地被暴露出来。相反,如果在好几个子类都用不到某个受保护成员,那么很可能应该将这个成员设为私有。经验表明,三个子类就足以测试一个可扩展的类了。而且这些子类应该由父类作者之外的人来编写。
When you design for inheritance a class that is likely to achieve wide use, realize that you are committing forever to the self-use patterns that you document and to the implementation decisions implicit in its protected methods and fields. These commitments can make it difficult or impossible to improve the performance or functionality of the class in a subsequent release. Therefore, you must test your class by writing subclasses before you release it.
当我们在设计一个可能被广泛继承的类时,我们必须意识到,我们已经永远承诺了文档里的自用模式,同时也承诺了类的受保护方法和域的实现决定。若我们想在后续的版本中提高性能或者改善类的功能,这些承诺可能会让其变得很困难不可能。因此,在发布这个类之前,我们一定要通过编写子类去测试这类。
Also, note that the special documentation required for inheritance clutters up normal documentation, which is designed for programmers who create instances of your class and invoke methods on them. As of this writing, there is little in the way of tools to separate ordinary API documentation from information of interest only to programmers implementing subclasses.
另外我们要注意的是,正常文档是设计给创建我们编写的类的实例,同时去调用这些类的方法的程序员用的,而为继承而写的特殊文档会打乱正常文档信息。在编写本文时,还没有什么好的工具可以将普通的API文档和仅针对实现子类的信息分离出来。
There are a few more restrictions that a class must obey to allow inheritance. Constructors must not invoke overridable methods, directly or indirectly. If you violate this rule, program failure will result. The superclass constructor runs before the subclass constructor, so the overriding method in the subclass will get invoked before the subclass constructor has run. If the overriding method depends on any initialization performed by the subclass constructor, the method will not behave as expected. To make this concrete, here’s a class that violates this rule:
若要让一个类允许被继承,有几点约束条件必须遵循。构造器一定不能调用可覆盖方法,无论是直接点用还是间接调用。如果违反了这条规则,将会导致程序失败。父类构造器在子类构造器之前运行,所以子类的覆盖方法会在子类构造器运行之前被调用。如果覆盖方法依赖于子类构造器的任意初始化动作,那么这个方法将不会产生预期行为。为了更具体地说明这一点,下面展示了一个违反这条规则的类:
public class Super {
// Broken - constructor invokes an overridable method
public Super() {
overrideMe();
}
public void overrideMe() { }
}
Here’s a subclass that overrides the overrideMe method, which is erroneously invoked by Super’s sole constructor:
接着是一个子类,它覆盖了overrideMe方法,而这个方法被父类的唯一构造器错误地调用了:
public final class Sub extends Super {
// Blank final, set by constructor
private final Instant instant;
Sub() {
instant = Instant.now();
}
// Overriding method invoked by superclass constructor
@Override
public void overrideMe() {
System.out.println(instant);
}
public static void main(String[] args) {
Sub sub = new Sub();
sub.overrideMe();
}
}
You might expect this program to print out the instant twice, but it prints out null the first time because overrideMe is invoked by the Super constructor before the Sub constructor has a chance to initialize the instant field. Note that this program observes a final field in two different states! Note also that if overrideMe had invoked any method on instant, it would have thrown a NullPointerException when the Super constructor invoked overrideMe. The only reason this program doesn’t throw a NullPointerException as it stands is that the println method tolerates null parameters.Note that it is safe to invoke private methods, final methods, and static methods, none of which are overridable, from a constructor.
你可能会期望这个程序会打印instant两次,但在第一次的时候它打印了一个null,因为在Sub类的构造器初始化instant域之前,overrideMe方法就被Super类的构造器调用了。注意,这个程序监控到了一个final域的两个不同状态!而且,如果overrideMe方法调用了instant的任意方法,那么,但Super类的构造器在调用overrideMe方法时,将抛出NullPointerException异常。上述程序之所以没有抛出NullPointerException异常是因为printlin方法可以接受并处理null参数。在构造器里调用不可覆盖的方法,即私有方法,final方法和静态方法,则是安全的。
The Cloneable and Serializable interfaces present special difficulties when designing for inheritance. It is generally not a good idea for a class designed for inheritance to implement either of these interfaces because they place a substantial burden on programmers who extend the class. There are, however, special actions that you can take to allow subclasses to implement these interfaces without mandating that they do so. These actions are described in Item 13 and Item 86.
在设计用于被继承的类时,Cloneable和Serializable接口会带来一些困难。通常情况下,让一个设计来被继承的类去实现无论这两个接口的哪一个都不是个好注意,因为它们会将一些实质性的负担放到扩展这个类的程序员身上。然而,我们还是可以采取一些特殊手段使得子类可以继承这些接口,而无需让编写子类的程序员去承担这些负担。条目13和条目86对这些手段进行了描述。
If you do decide to implement either Cloneable or Serializable in a class that is designed for inheritance, you should be aware that because the clone and readObject methods behave a lot like constructors, a similar restriction applies: neither clone nor readObject may invoke an overridable method, directly or indirectly. In the case of readObject, the overriding method will run before the subclass’s state has been deserialized. In the case of clone, the overriding method will run before the subclass’s clone method has a chance to fix the clone’s state. In either case, a program failure is likely to follow. In the case of clone, the failure can damage the original object as well as the clone. This can happen, for example, if the overriding method assumes it is modifying the clone’s copy of the object’s deep structure, but the copy hasn’t been made yet.
如果你决定让设计用来被继承的类实现Cloneable接口或Serializable接口,那么你应该意识到,由于clone方法和readObject方法的行为像构造器,所以类似的约束也是适用:无论是clone方法还是readObject方法,都不应该直接或间接地调用一个可覆盖方法。若在这两个方法里调用了可覆盖方法,那么,对于readObject方法,可覆盖方法将会在子类状态被反序列化之前运行;对于clone方法,可覆盖方法将会在子类的clone方法有机会修正克隆对象的状态之前运行。对于这两种情况,都有可能导致程序失败。对于clone方法,程序失败同时会破坏原始对象和克隆对象。例如,如果覆盖方法假定它正在修改对象深层架构的克隆对象的拷贝,但同时这个拷贝却还没有完成,那么就会发生这种现象。
Finally, if you decide to implement Serializable in a class designed for inheritance and the class has a readResolve or writeReplace method, you must make the readResolve or writeReplace method protected rather than private. If these methods are private, they will be silently ignored by subclasses. This is one more case where an implementation detail becomes part of a class’s API to permit inheritance.
最后,如果你决定让设计用来被继承的类实现Serializable接口,而且这个类拥有readResolve方法或writeReplace方法,你一定要把readResolve方法或writeReplace方法设为受保护的,而不是私有的。如果这些方法是私有的,它们将会被子类忽略掉。这也是“为了允许继承而把实现细节变成类API的一部分”的另一种情形。
By now it should be apparent that designing a class for inheritance requires great effort and places substantial limitations on the class. This is not a decision to be undertaken lightly. There are some situations where it is clearly the right thing to do, such as abstract classes, including skeletal implementations of interfaces (Item 20). There are other situations where it is clearly the wrong thing to do, such as immutable classes (Item 17).
现在,我们应该能明显感觉到,设计一个用来被继承的类需要很大的努力,而且对这个类有很大的限制。做这个决定不是一件轻而易举的事。当然,也有一些场景,这么做很明显是对的,例如抽象类,包括接口的骨架实现(skeletal implementation)(条目20)。在另一些场景里,这么做就明显是错的,例如不可变类(条目17)
But what about ordinary concrete classes? Traditionally, they are neither final nor designed and documented for subclassing, but this state of affairs is dangerous. Each time a change is made in such a class, there is a chance that subclasses extending the class will break. This is not just a theoretical problem. It is not uncommon to receive subclassing-related bug reports after modifying the internals of a nonfinal concrete class that was not designed and documented for inheritance.
但是,对于普通的具体类应该怎么办呢?一般情况下,它们既不是final的,也不是设计来被子类化的,同时也没有文档说明,但是这种状况是危险的。每次这种类做了更改,那些继承了这个类的子类就有可能被破坏。这不仅仅只是个理论问题。对于一个并非设计来被继承同时又没有关于继承的说明文档的非final具体类,若修改了其内部信息,总会收到子类相关的bug报告,而且这种情况还挺常见的。
The best solution to this problem is to prohibit subclassing in classes that are not designed and documented to be safely subclassed. There are two ways to prohibit subclassing. The easier of the two is to declare the class final. The alternative is to make all the constructors private or package-private and to add public static factories in place of the constructors. This alternative, which provides the flexibility to use subclasses internally, is discussed in Item 17. Either approach is acceptable.
解决这个问题最好的办法是,对于并非为了安全被子类化而设计和编写文档的类,应该禁止对其进行子类化。有两种方式可以阻止子类化。最简单的方式就是将这个类声明为final。另一种方式就是将所有的构造器设为私有或者包级私有,同时添加公有静态工厂来替代构造器。这种方式为内部使用子类提供了灵活性,我们在条目17里讨论过。任意一种方式都是可以接受的。
This advice may be somewhat controversial because many programmers have grown accustomed to subclassing ordinary concrete classes to add facilities such as instrumentation, notification, and synchronization or to limit functionality. If a class implements some interface that captures its essence, such as Set, List, or Map, then you should feel no compunction about prohibiting subclassing. The wrapper class pattern, described in Item 18, provides a superior alternative to inheritance for augmenting the functionality.
本条目的建议可能会引起争议,因为很多程序员已经习惯于继承普通的具体类并往里添加功能,如仪表盘功能(instrumentation),通知功能(notification)和同步功能(synchronization)或者限制原有的一些功能。如果一个类实现了一些反映本质的接口,如Set,List,或者Map,那么你不要为禁止了继承而感到后悔。条目18里描述的包装者类模式提供了更好的方式,让继承实现更多的功能。
If a concrete class does not implement a standard interface, then you may inconvenience some programmers by prohibiting inheritance. If you feel that you must allow inheritance from such a class, one reasonable approach is to ensure that the class never invokes any of its overridable methods and to document this fact. In other words, eliminate the class’s self-use of overridable methods entirely. In doing so, you’ll create a class that is reasonably safe to subclass. Overriding a method will never affect the behavior of any other method.
如果一个具体类没有实现一个标准的接口,那么如果禁止这个类被继承将会为部分程序员带来不便。如果你觉得你一定要允许一个类被继承,那么一个合理的方式是保证这个类永远不调用它的任一可覆盖方法,并在文档里说明这个事实。换句话说,要完全消除类对它的可覆盖方法的自用性。这么做之后,你将能编写出一个能够被安全子类化的类,子类即使覆盖某个方法也不会影响其它方法的行为。
You can eliminate a class’s self-use of overridable methods mechanically, without changing its behavior. Move the body of each overridable method to a private “helper method” and have each overridable method invoke its private helper method. Then replace each self-use of an overridable method with a direct invocation of the overridable method’s private helper method.
你可以机械地消除一个类对其可覆盖方法的自用,而且不用改变它的行为。将每个可覆盖方法的方法体移到一个私有的“辅助方法(helper method)”里并让每个可覆盖方法调用其对应的私有的辅助方法。然后将每个可覆盖方法的自用替换为直接调用可覆盖方法的私有辅助方法。
In summary, designing a class for inheritance is hard work. You must document all of its self-use patterns, and once you’ve documented them, you must commit to them for the life of the class. If you fail to do this, subclasses may become dependent on implementation details of the superclass and may break if the implementation of the superclass changes. To allow others to write efficient subclasses, you may also have to export one or more protected methods. Unless you know there is a real need for subclasses, you are probably better off prohibiting inheritance by declaring your class final or ensuring that there are no accessible constructors.
总之,设计一个用来被继承的类是件不容易的事。我们必须在文档里说明该类的自用模式,而且一旦我们做出说明后,必须在该类的整个生命周期做出承诺。如果不这么做,子类可能就会依赖于父类的实现细节,而且如果父类的实现出现了变更,子类还有可能被破坏。为了让别人能编写出有效的子类,我们可能也需要导出一个或多个受保护方法。除非我们知道某个类的确是要被子类化,否则最好将类声明为final或者保证其没有可访问的构造器来禁止该类被继承。