×

消息

EU e-Privacy Directive

This website uses cookies to manage authentication, navigation, and other functions. By using our website, you agree that we can place these types of cookies on your device.

View e-Privacy Directive Documents

You have declined cookies. This decision can be reversed.

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

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)。

提交评论


安全码
刷新