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 or constructor, the
documentation must indicate which overridable methods the method or constructor
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.
Here’s an
example, copied from the specification for 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 (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.
This
documentation leaves no doubt that overriding the iterator method will affect the behavior of the remove method. Furthermore, it 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 16, where the programmer subclassing HashSet simply could not say whether overriding the
add method would affect the behavior of the addAll method.
Design for
inheritance involves more than just documenting patterns of selfuse. 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:
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 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.
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!
The only way to test a class designed for inheritance
is to write subclasses.
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:
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();
}
}
You might
expect this program to print out the date twice, but it prints out null the first time, because the overrideMe method is invoked by the Super constructor before the Sub constructor has a chance to initialize the date field. Note that this program observes a
final field in two different states! Note also that if overrideMe had invoked any method on date, the invocation 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 has
special provisions for dealing with a null argument.
If you do
decide to implement Cloneable or Serializable in a class 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 the readObject method, the
overriding method will run
before the
subclass’s state has been deserialized. In the case of the clone method, the overriding method will run before
the subclass’s clone method has a
chance to fix the clone’s state.
Designing a
class for inheritance places substantial limitations on the class.
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.
Reference: Effective Java 2nd Edition by Joshua Bloch