Chapter 4. Interfaces

 

“Conducting” is when you draw “designs” in the nowhere—with your stick, or with your hands—which are interpreted as “instructional messages” by guys wearing bow ties who wish they were fishing.

 
 --Frank Zappa

The fundamental unit of programming in the Java programming language is the class, but the fundamental unit of object-oriented design is the type. While classes define types, it is very useful and powerful to be able to define a type without defining a class. Interfaces define types in an abstract form as a collection of methods or other types that form the contract for that type. Interfaces contain no implementation and you cannot create instances of an interface. Rather, classes can expand their own types by implementing one or more interfaces. An interface is an expression of pure design, whereas a class is a mix of design and implementation.

A class can implement the methods of an interface in any way that the designer of the class chooses. An interface thus has many more possible implementations than a class. Every major class in an application should be an implementation of some interface that captures the contract of that class.

Classes can implement more than one interface. The Java programming language allows multiple inheritance of interface but only single inheritance of implementation—a class can extend only one other class. Classes can use inheritance of interfaces to expand their type and then use, for example, composition to provide an implementation for those interfaces. This design allows the typing flexibility of multiple inheritance while avoiding the pitfalls of multiple implementation inheritance, at the cost of some additional work for the programmer.

In a given class, the classes that are extended and the interfaces that are implemented are collectively called the supertypes, and from the viewpoint of the supertypes, the new class is a subtype. The new class includes all its supertypes, so a reference to an object of the subtype can be used polymorphically anywhere a reference to an object of any of its supertypes (class or interface) is required. Interface declarations create type names just as class declarations do; you can use the name of an interface as the type name of a variable, and any object whose class implements that interface can be assigned to that variable.

A Simple Interface Example

Many simple interfaces define a property that is ascribable to a variety of different objects from different classes. These properties are often defined in terms of an object being “able” to do something. For example, in the standard packages there are a number of “ability” interfaces, such as:

  • Cloneable—. Objects of this type support cloning, as you learned in detail on page 101.

  • Comparable—. Objects of this type have an ordering that allows them to be compared.

  • Runnable—. Objects of this type represent a unit of work, that can often execute in an independent thread of control (see Chapter 14).

  • Serializable—. Objects of this type can be written to an object byte stream for shipping to a new virtual machine, or for storing persistently and then reconstituting into a live object (see “Object Serialization” on page 549).

Let's look at the Comparable interface in more detail. This interface can be implemented by any class whose objects can be compared to each other according to the class's “natural ordering.” The interface contains a single method:

public interface Comparable<T> {
    int compareTo(T obj);
}

An interface declaration is similar to a class declaration, except that the keyword interface is used instead of class. There are also special rules concerning the members of an interface, as you will soon learn.

The compareTo method takes a single object argument of type T and compares it to the current object (expected to also be of type T), returning a negative, zero, or positive integer if the current object is less than, equal to, or greater than the argument, respectively.

Consider a variation of the Point class we introduced in Chapter 1. The natural ordering for points could be their distance from the origin. We could then make Point objects Comparable:

class Point implements Comparable<Point> {

    /** Origin reference that never changes */
    private static final Point ORIGIN = new Point();

    private int x, y;

    // ... definition of constructors, setters and accessors

    public double distance(Point p) {
        int xdiff = x - p.x;
        int ydiff = y - p.y;
        return Math.sqrt(xdiff * xdiff + ydiff * ydiff);
    }

    public int compareTo(Point p) {
        double pDist = p.distance(ORIGIN);
        double dist  = this.distance(ORIGIN);
        if (dist > pDist)
            return 1;
        else if (dist == pDist)
            return 0;
        else
            return -1;
    }
}

First we declare that Point is a Comparable class. A class identifies the interface types that it implements by listing them after the keyword implements, before the class body is defined (and after any extends clause). All such interfaces are the superinterfaces of the class. The class must provide an implementation for all of the methods defined in its superinterfaces, or else the class must be declared abstract, thereby requiring that any non-abstract subclass implement them.

The only method we need to implement for the Comparable interface is compareTo, and to actually compare two points we simply compare their distance from the origin.

Interfaces introduce type names just as classes do, so you can declare variables of those types. For example:

Comparable<Point> p1;

In fact much of the power of interfaces comes from declaring and using only variables of interface type rather than of some specific class type. For example, you can define a general-purpose sort routine that can sort any array of Comparable objects without regard to what the class of those objects actually is—all the objects in the array should be the same type of course:[1]

class Sorter {
    static Comparable<?>[] sort(Comparable<?>[] list) {
        // implementation details ...
        return list;
    }
}

References of interface type, however, can be used only to access members of that interface. For example, the following will produce a compile-time error:

Comparable<Point> obj = new Point();
double dist = obj.distance(p1); // INVALID: Comparable has
                                // no distance method

If you want to treat obj as a Point object you must explicitly cast it to that type.

You can invoke any of the Object methods using a reference of an interface type because no matter what interfaces the object implements, it is always an Object and so has those methods. In fact, any interface that does not extend some other interface implicitly has members matching each of the public methods of Object (unless the interface explicitly overrides them). Hence, the following is legal:

String desc = obj.toString();

as is assigning an interface reference to an Object reference.

Interface Declarations

An interface is declared using the keyword interface, giving the interface a name and listing the interface members between curly braces.

An interface can declare three kinds of members:

  • constants (fields)

  • methods

  • nested classes and interfaces

All interface members are implicitly public, but, by convention, the public modifier is omitted. Having non-public members in an interface would make little sense; where it does make sense you can use the accessibility of the interface itself to control access to the interface members.

We defer a discussion of nested classes and interfaces until Chapter 5.

Interface Constants

An interface can declare named constants. These constants are defined as fields but are implicitly public, static, and final—again, by convention, the modifiers are omitted from the field declarations. These fields must also have initializers—blank finals are not permitted. Annotations can also be applied to the fields—see Chapter 15.

Because interfaces contain no implementation details, they cannot define normal fields—such a definition would be dictating implementation policy to the classes that choose to implement the interface. Interfaces can define named constants because these are useful in the design of types. For example, an interface that had differing levels of verbosity in its contract might have the following:

interface Verbose {
    int SILENT  = 0;
    int TERSE   = 1;
    int NORMAL  = 2;
    int VERBOSE = 3;

    void setVerbosity(int level);
    int getVerbosity();
}

SILENT, TERSE, NORMAL, and VERBOSE can be passed to the setVerbosity method, giving names to constant values that represent specific meanings. In this particular case, the verbosity levels could be more effectively represented as the constants of an enumeration type nested within the Verbose interface. Enumeration types are discussed in Chapter 6.

If you need shared, modifiable data in your interface you can achieve this by using a named constant that refers to an object that holds the data. A nested class is good for defining that object, so we defer an example until Chapter 5.

Interface Methods

The methods declared in an interface are implicitly abstract because no implementation is, or can be, given for them. For this reason the method body is simply a semicolon after the method header. By convention, the abstract modifier on the method declaration is omitted.

No other method modifiers are permitted on an interface method declaration, except for annotations—see Chapter 15. They are implicitly public and so can have no other access modifier. They cannot have modifiers that define implementation characteristics—such as native, synchronized, or strictfp—because an interface does not dictate implementation, and they cannot be final because they haven't been implemented yet. Of course, the implementation of these methods within a specific class can have any of these modifiers. Interface methods can never be static because static methods can't be abstract.

Interface Modifiers

An interface declaration can be preceded by interface modifiers:

  • annotations—. Annotations and annotation types are discussed in Chapter 15.

  • public—. A public interface is publicly accessible. Without this modifier an interface is only accessible within its own package.

  • abstract—. All interfaces are implicitly abstract because their methods are all abstract—they have no implementation. Again, by convention, the abstract modifier is always omitted.

  • strict floating point—. An interface declared strictfp has all floating-point arithmetic, defined within the interface, evaluated strictly. This applies to initialization expressions for constants and to all nested types declared in the interface. In contrast to classes, this does not imply that each method in the interface is implicitly strictfp because that is an implementation detail. See Section 9.1.3 on page 203 for details.

When multiple modifiers are applied to the same interface declaration, we recommend using the order listed above.

Extending Interfaces

Interfaces can be extended using the extends keyword. Interfaces, unlike classes, can extend more than one other interface:

public interface SerializableRunnable
                 extends java.io.Serializable, Runnable
{
    // ...
}

The SerializableRunnable interface extends both java.io.Serializable and Runnable, which means that all methods and constants defined by those interfaces are now part of the SerializableRunnable contract, together with any new methods and constants it defines. The interfaces that are extended are the superinterfaces of the new interface and the new interface is a subinterface of its superinterfaces.

Because interfaces support multiple inheritance, the inheritance graph can contain multiple paths to the same superinterface. This means that constants and methods can be accessed in different ways. However, because interfaces define no implementation of methods, and provide no per-object fields, there are no issues regarding the semantics of this form of multiple inheritance.

Inheriting and Hiding Constants

An extended interface inherits all the constants declared in its superinterfaces. If an interface declares a constant of the same name as an inherited constant, regardless of their types, then the new constant hides the inherited one; this is the same hiding of fields described in “Hiding Fields” on page 86. In the subinterface and in any object implementing the subinterface, any reference to the constant using its simple name will refer to the constant defined in the subinterface. The inherited constant can still be accessed with the qualified name of the constant, that is, the interface name followed by dot and then the constant name—the usual way of referring to static members.

interface X {
    int val = 1;
}
interface Y extends X {
    int val = 2;
    int sum = val + X.val;
}

Interface Y has two constants: val and sum. From inside Y, to refer to the hidden val in its superinterface you must qualify it as X.val. Externally, you can access the constants of Y by using the normal static forms of Y.val and Y.sum, and of course you can access X's val constant by using X.val.

These rules are, of course, identical to those concerning the inheritance of static fields in classes.

When a class implements Y you can access the constants in Y as though they were constants declared in the class. For example, given

class Z implements Y { }

you can do

System.out.println("Z.val=" + Z.val + ", Z.sum=" + Z.sum);

but there is no way to refer to X.val via Z. However, given an instance of Z you can use an explicit cast to access X.val:

Z z = new Z();
System.out.println("z.val=" + z.val +
                   ", ((Y)z).val=" + ((Y)z).val +
                   ", ((X)z).val=" + ((X)z).val);

which prints out

z.val=2, ((Y)z).val=2, ((X)z).val=1

as you would expect. Again these are the same rules that apply to static fields in extended classes—it doesn't matter whether a class inherits a static field from a superclass or a superinterface.

While all these rules are necessary from a language perspective, they have little practical consequence—there are few reasons to hide existing fields, and all accesses to static fields, whether class or interface, should be via the name of the type in which that field is declared.

If an interface inherits two or more constants with the same name, then any simple reference to the constant is ambiguous and results in a compile-time error. For example, given the previous interface declarations and the following:

interface C {
    String val = "Interface C";
}
interface D extends X, C { }

then the expression D.val is ambiguous—does it mean the integer val or the String reference val? Inside D you would have to explicitly use X.val or C.val.

A class that implements more than one interface, or that extends a class and implements one or more interfaces, can experience the same hiding and ambiguity issues as an interface that extends more than one interface. The class's own static fields can hide the inherited fields of the interfaces it implements or the class it extends, and simple references to multiply-inherited non-hidden fields will be ambiguous.

Inheriting, Overriding, and Overloading Methods

A subinterface inherits all the methods declared in its superinterfaces. If a declared method in a subinterface has the same signature (name and parameter list) as an inherited method and the same, or covariant, return type, then the new declaration overrides any and all existing declarations. Overriding in interfaces, unlike overriding in classes, has no semantic effect—the interface effectively contains multiple declarations of the same method, but in any one implementing class there can only be one implementation of that method.

Similarly, if an interface inherits more than one method with the same signature, or if a class implements different interfaces containing a method with the same signature, there is only one such method. The implementation of this method is ultimately defined by the class implementing the interfaces, and there is no ambiguity there. If the methods have the same signature but different return types, then one of the return types must be a subtype of all the others, otherwise a compile-time error occurs. The implementation must define a method that returns that common subtype.

The real issue is whether a single implementation of the method can honor all the contracts implied by that method being part of the different interfaces. This may be an impossible requirement to satisfy in some circumstances. For example:

interface CardDealer {
    void draw();          // flip top card
    void deal();          // distribute cards
    void shuffle();
}
interface GraphicalComponent {
    void draw();          // render on default device
    void draw(Device d);  // render on 'd'
    void rotate(int degrees);
    void fill(Color c);
}
interface GraphicalCardDealer
    extends CardDealer, GraphicalComponent { }

Here it is difficult to write an implementation of draw() that can satisfy the two different contracts independently. If you try to satisfy them simultaneously, you are unlikely to achieve the desired results: flipping a card each time the screen gets repainted.

As with overriding in class extension, the overriding method is not permitted to throw more checked exceptions than the method it overrides. If two or more method declarations are inherited, without overriding, and differ in the exceptions they throw, then the implementation of that method must satisfy all the throws clauses of those declarations. Again the main issue is whether such distinct methods can have a single implementation that honors all contracts. We look further at the issues of overriding and exception throwing in Chapter 12.

If a declared method has the same name but different parameters from an inherited method, then the declared method is an overloaded form of the inherited method. The eventual class implementation will provide a method body for each of the overloaded forms.

If a declared method differs only in return type from an inherited method, or if two inherited methods differ only in return type where one type is not a subtype of the other, you will get a compile-time error.

Working with Interfaces

The previous chapter introduced the Attr class and showed how to extend it to make specialized types of attribute objects. Now all you need is the ability to associate attributes with objects.

The first decision to make is whether having attributes is reflected in the type of the object. An object could, if you chose, contain a set of attributes and allow programmers access to that set. Or you could say that being able to store attributes on an object is a part of its type and so should be part of the type hierarchy. Both positions are legitimate. We believe that representing the ability to hold attributes in the type hierarchy is most useful. We will create an Attributed type to be used for objects that can be attributed by attaching Attr objects to them.

To create an Attributed type you could define a class that would form the superclass for all attributed objects. But then programmers must decide whether to inherit from Attributed or from some other useful class. Instead we make Attributed into an interface:

public interface Attributed {
    void add(Attr newAttr);
    Attr find(String attrName);
    Attr remove(String attrName);
    java.util.Iterator<Attr> attrs();
}

This interface declares four methods: one for adding a new attribute to an Attributed object; one for finding whether an attribute of a given name has been added to that object; one for removing an attribute from an object; and one for accessing all of the attributes currently attached to the object.

When we add an attribute to an object, that object considers itself the owner of that Attr instance until it is removed. If the attribute has its value changed or is shared among a number of objects, then the expectation is that the programmer makes such changes and performs such sharing in a manner that makes sense to the application. If the programmer is not to be trusted in this, then methods should specify that they make defensive copies of parameters, and/or return values, such that no harmful changes can occur.

The attributes are accessed through an Iterator object returned from the attrs method. Iterator is a generic interface defined in java.util for collection classes to use to provide access to their contents (see “Iteration” on page 571). In effect, the Attributed interface defines a collection type—a set of attributes—so we use the normal mechanism for accessing the contents of a collection, namely, the Iterator type. Using Iterator has another benefit: It is easy to implement Attributed with a standard collection class (such as HashMap) that uses Iterator, as you'll soon see.

Many classes that provide an Iterator declare that they implement the Iterable interface, which defines the single method iterator to return an Iterator instance. Although an Attributed object does provide an Iterator, it would be wrong to have Attributed extend Iterable or to rename attrs as iterator, because that would restrict the ability of the class implementing Attributed to control its own iteration behavior. For example, it would mean that an Attributed collection class would not be able to provide an iterator for its elements rather than its attributes.

Implementing Interfaces

Interfaces describe contracts in a pure, abstract form, but an interface is interesting only if a class implements it.

Some interfaces are purely abstract—they do not have any useful general implementation but must be implemented afresh for each new class. Most interfaces, however, may have several useful implementations. In the case of our Attributed interface, we can imagine several possible implementations that use various strategies to store a set of attributes.

One strategy might be simple and fast when only a few attributes are in a set; another one might be optimized for attribute sets that are queried more often than they are changed; yet another design might be optimized for sets that change frequently. If there were a package of various implementations for the Attributed interface, a class might choose to implement the Attributed interface through any one of them or through its own implementation.

As an example, here is a simple implementation of Attributed that uses the utility java.util.HashMap class. The class AttributedImpl declares that it implements the interface Attributed, so the class must implement all the interface's methods. AttributedImpl implements the methods using a HashMap, described in “HashMap” on page 590. Later, this implementation is used to implement the Attributed interface for a specific set of objects to which you would want to add attributes. First, here is the AttributedImpl class:

import java.util.*;

class AttributedImpl implements Attributed, Iterable<Attr> {
    protected Map<String, Attr> attrTable =
        new HashMap<String, Attr>();

    public void add(Attr newAttr) {
        attrTable.put(newAttr.getName(), newAttr);
    }

    public Attr find(String name) {
        return attrTable.get(name);
    }

    public Attr remove(String name) {
        return attrTable.remove(name);
    }

    public Iterator<Attr> attrs() {
        return attrTable.values().iterator();
    }

    public Iterator<Attr> iterator() {
        return attrs();
    }
}

The initializer for attrTable creates a HashMap object to hold attributes. This HashMap object does most of the actual work. The HashMap class uses the key object's hashCode method to hash any object it is given as a key. No explicit hash method is needed since String already provides a good hashCode implementation.

When a new attribute is added, the Attr object is stored in the hash map under its name, and then you can easily use the hash map to find and remove attributes by name. The attrs method returns the Iterator for the hash map's values, giving access to all the attributes of the current object.

We chose to make this implementation of Attributed also implement Iterable<Attr>, as the attributes are the only things an AttributedImpl contains. To do this, we had to define the iterator method to return the same value as the attrs method.

Using an Implementation

You can use an implementing class like AttributedImpl by simply extending the class. This is the simplest tool when it is available because all the methods and their implementations are inherited. But if you need to support more than one interface or extend a different class, you must use a different approach. The most common approach is to create an object of an implementing class and forward all the methods of the interface to that object, returning any values—this is often called composition.

In composition and forwarding, each method in the class that is inherited from the interface invokes the implementation from another object and returns the result. Here is an implementation of the Attributed interface that uses an AttributedImpl object to build an attributed version of our previously defined celestial body class Body:

import java.util.Iterator;

class AttributedBody extends Body
    implements Attributed
{
    private AttributedImpl attrImpl = new AttributedImpl();

    public AttributedBody() {
        super();
    }

    public AttributedBody(String name, Body orbits) {
        super(name, orbits);
    }

    // Forward all Attributed methods to the attrImpl object

    public void add(Attr newAttr)
        { attrImpl.add(newAttr); }
    public Attr find(String name)
        { return attrImpl.find(name); }
    public Attr remove(String name)
        { return attrImpl.remove(name); }
    public Iterator<Attr> attrs()
        { return attrImpl.attrs(); }
}

The declaration that AttributedBody extends Body and implements Attributed defines the contract of AttributedBody. The implementations of all Body's methods are inherited from the Body class itself. Each method of Attributed is implemented by forwarding the invocation to the AttributedImpl object's equivalent method, returning its value (if any). This also means that you must add a field of type AttributedImpl to use in the forwarding methods and initialize that field to refer to an AttributedImpl object.

Forwarding is both straightforward and much less work than implementing Attributed from scratch. Forwarding also enables you to quickly change the implementation you use, should a better implementation of Attributed become available at some future date. However, forwarding must be set up manually and that can be tedious and sometimes error prone.

Marker Interfaces

Some interfaces do not declare any methods but simply mark a class as having some general property. The Cloneable interface is such a marker interface—it has neither methods nor constants, but marks a class as partaking in the cloning mechanism (see page 101).

Marker interfaces are the degenerate case of a contract because they define no language-level behavior—no methods or values. All their contract is in the documentation that describes the expectations you must satisfy if your class implements that interface. The interfaces Serializable and Externalizable (described in “Object Serialization” on page 549) are marker interfaces, as are both java.rmi.Remote (see “java.rmi — Remote Method Invocation” on page 727) and java.util.EventListener (see “java.awt — The Abstract Window Toolkit” on page 717).

Marker interfaces can have a profound impact on the behavior of the classes that implement them—consider Cloneable. Do not be fooled into thinking that they are unimportant merely because they have no methods.

When to Use Interfaces

An interface defines a type with an abstract contract. An abstract class also defines a type with an abstract contract. Which should you use and when?

There are two major differences between interfaces and abstract classes:

  • Interfaces provide a form of multiple inheritance, because you can implement multiple interfaces. A class can extend only one other class, even if that class has only abstract methods.

  • An abstract class can have a partial implementation, protected parts, static methods, and so on, whereas interfaces are limited to public constants and public methods with no implementation.

These differences usually direct the choice of which tool is best to use in a particular implementation. If multiple inheritance is important or even useful, interfaces are used. However, an abstract class enables you to provide some or all of the implementation so that it can be inherited easily, rather than by explicit forwarding. Additionally, an abstract class can control the implementation of certain methods by making them final—for example, our SortDouble class in Chapter 3 ensures that sorting is done correctly. However, if you find yourself writing an abstract class with all abstract methods, you're really writing an interface.

Any major class you expect to be extended, whether abstract or not, should be an implementation of an interface. Although this approach requires a little more work on your part, it enables a whole category of use that is otherwise precluded. For example, suppose we had created an Attributed class instead of an Attributed interface with an AttributedImpl implementation class. In that case, programmers who wanted to create new classes that extended other existing classes could never use Attributed, since you can extend only one class—the class AttributedBody could never have been created. Because Attributed is an interface, programmers have a choice: They can extend AttributedImpl directly and avoid the forwarding, or, if they cannot extend, they can at least use composition and forwarding to implement the interface. And if the general implementation provided is incorrect, they can write their own implementation. You can even provide multiple possible implementations of the interface to prospective users. Whatever implementation strategy programmers prefer, the objects they create are Attributed.

Exercise 4.1Rewrite your solution to Exercise 3.6 on page 99 using an interface for EnergySource instead of an abstract class.

Exercise 4.2Rewrite your solution to Exercise 3.12 on page 114 using an interface if you didn't write it that way in the first place.

Exercise 4.3Should the LinkedList class from previous exercises be an interface? Rewrite it that way with an implementation class before you decide.

Exercise 4.4Design a collection class hierarchy using only interfaces.

Exercise 4.5Think about whether the following types should be represented as interfaces, abstract classes, or concrete classes: (a) TreeNode to represent nodes in an N-ary tree; (b) TreeWalker to walk the tree in a particular order (such as depth-first or breadth-first); (c) Drawable for objects that can be drawn by a graphics system; (d) Application for programs that can be run from a graphical desktop.

Exercise 4.6What changes in your assumptions about each of the problems in Exercise 4.5 would make you change your answers?

 

There are two ways of constructing a software design: one way is to make it so simple that there are obviously no deficiencies; the other is to make it so complicated that there are no obvious deficiencies.

 
 --C.A.R. Hoare


[1] In Chapter 11 you learn about generic methods, and sort should really be defined as such.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.99.152