The Java
programming language provides two mechanisms for defining a type that permits
multiple implementations: interfaces and abstract classes. The most obvious
difference between the two mechanisms is that abstract classes are permitted to
contain implementations for some methods while interfaces are not.
Existing classes
can be easily retrofitted to implement a new interface. All you have
to do is add the required methods if they don’t yet exist and add an implements clause to the class declaration. For example,
many existing classes were retrofitted to implement the Comparable interface when it was introduced into the
platform. Existing classes cannot, in general, be retrofitted to extend a new
abstract class.
Interfaces are
ideal for defining mixins. Loosely speaking, a mixin is a type that a class can implement in addition to its “primary
type” to declare that it provides some optional behavior. For example, Comparable is a mixin interface that allows a class to
declare that its instances are ordered with respect to other mutually
comparable objects.
Interfaces allow
the construction of nonhierarchical type frameworks. Type
hierarchies are great for organizing some things, but other things don’t fall
neatly into a rigid hierarchy. For example, suppose we have an interface
representing a singer and another representing a songwriter:
public interface Singer {
AudioClip sing(Song s);
}
public interface Songwriter {
Song compose(boolean hit);
}
In real life,
some singers are also songwriters. Because we used interfaces rather than
abstract classes to define these types, it is perfectly permissible for a
single class to implement both Singer and Songwriter. In fact, we can define a third interface
that extends both Singer and Songwriter and adds new methods that are appropriate to
the combination:
public interface SingerSongwriter extends Singer,
Songwriter {
AudioClip strum();
void actSensitive();
}
You don’t
always need this level of flexibility, but when you do, interfaces are a
lifesaver.
Interfaces
enable safe, powerful functionality enhancements via the wrapper class idiom, described in Item 16. If you use abstract classes to define
types, you leave the programmer who wants to add functionality with no
alternative but to use inheritance. The resulting classes are less powerful and
more fragile than wrapper classes.
While
interfaces are not permitted to contain method implementations, using
interfaces to define types does not prevent you from providing implementation
assistance to programmers. You can combine the virtues of interfaces and
abstract classes by providing an abstract skeletal implementation class to go with each nontrivial interface
that you export.
By convention,
skeletal implementations are called AbstractInterface, where Interface is the name of
the interface they implement. For example, the Collections Framework provides a
skeletal implementation to go along with each main collection interface: AbstractCollection, AbstractSet, AbstractList, and AbstractMap. Arguably it
would have made sense to call them SkeletalCollection,
SkeletalSet, SkeletalList, and SkeletalMap, but the Abstract
convention
is now firmly established.
When properly
designed, skeletal implementations can make it very
easy
for programmers to provide their own implementations of your interfaces. For
example, here’s a static factory method containing a complete, fully functional
List implementation:
// Concrete implementation built atop skeletal
implementation
static List<Integer> intArrayAsList(final int[] a)
{
if (a == null)
throw new NullPointerException();
return new AbstractList<Integer>() {
public Integer get(int i) {
return a[i]; // Autoboxing (Item 5)
}
@Override public Integer set(int i, Integer val) {
int oldVal = a[i];
a[i] = val; // Auto-unboxing
return oldVal; // Autoboxing
}
public int size() {
return a.length;
}
};
}
When you
consider all that a List implementation does for you, this
example is an impressive demonstration of the power of skeletal
implementations.
The beauty of
skeletal implementations is that they provide the implementation assistance of
abstract classes without imposing the severe constraints that abstract classes
impose when they serve as type definitions.
For example,
here’s a skeletal implementation of the Map.Entry
interface:
// Skeletal Implementation
public abstract class AbstractMapEntry<K,V>
implements Map.Entry<K,V> {
// Primitive operations
public abstract K getKey();
public abstract V getValue();
// Entries in modifiable maps must override this method
public V setValue(V value) {
throw new UnsupportedOperationException();
}
// Implements the general contract of Map.Entry.equals
@Override public boolean equals(Object o) {
if (o == this)
return true;
if (! (o instanceof Map.Entry))
return false;
Map.Entry<?,?> arg = (Map.Entry) o;
return equals(getKey(), arg.getKey()) &&
equals(getValue(), arg.getValue());
}
private static boolean equals(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}
// Implements the general contract of Map.Entry.hashCode
@Override public int hashCode() {
return hashCode(getKey()) ^ hashCode(getValue());
}
private static int hashCode(Object obj) {
return obj == null ? 0 : obj.hashCode();
}
}
Because
skeletal implementations are designed for inheritance, you should follow all of
the design and documentation guidelines in Item 17.
Using abstract
classes to define types that permit multiple implementations has one great
advantage over using interfaces: It is far easier to evolve an abstract class
than an interface.
Once an
interface is released and widely implemented, it is almost impossible to
change.
To summarize,
an interface is generally the best way to define a type that permits multiple
implementations. An exception to this rule is the case where ease of evolution
is deemed more important than flexibility and power. Under these circumstances,
you should use an abstract class to define the type, but only if you understand
and can accept the limitations. If you export a nontrivial interface, you
should strongly consider providing a skeletal implementation to go with it.
Finally, you should design all of your public interfaces with the utmost care
and test them thoroughly by writing multiple implementations.
Reference: Effective Java 2nd Edition by Joshua Bloch