类要么被设计成专用于继承(并辅以清楚的文档),要么就完全禁止继承

item 16提到过,继承一个本软件包以外的类是危险的,除非它被设计成专用于继承。这节就是讲一个"专用于继承的类"该是什么样子。

首先,这个类必须用文档注明它自己对"可覆盖方法"的使用情况(所谓"可覆盖方法",就是非final的、并且是public或protected的方法)。比如:某个方法调用了哪些可覆盖方法,调用顺序怎样,它们的返回值对后续处理将产生怎样的影响。还应注明在哪些情况下,可覆盖方法会被调用。比如调用者可能是后台运行的其他线程或静态初始化代码。

作为一种通用约定,如果某个方法调用了可覆盖方法,那么在它文档注释的末尾,要加上对这些调用的描述。这些描述需要以"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)。

提交评论


安全码
刷新