6.4 Interfaces

Extending classes using single implementation inheritance creates new class types. A superclass reference can denote objects of its own type and its subclasses strictly according to the inheritance hierarchy. Because this relationship is linear, it rules out multiple implementation inheritance, that is, a subclass inheriting from more than one superclass. Instead Java provides interfaces, which not only allow new named reference types to be introduced, but also permit multiple interface inheritance.

Defining Interfaces

A top-level interface has the following general syntax:


<accessibility modifier> interface <interface name>
                              <extends interface clause> // Interface header

{ // Interface body
     <constant declarations>
     <method prototype declarations>
     <nested class declarations>
     <nested interface declarations>
   }

In the interface header, the name of the interface is preceded by the keyword interface. In addition, the interface header can specify the following information:

  • scope or accessibility modifier (see Section 4.7, p. 131)

  • any interfaces it extends (see Section 6.4, p. 254)

The interface body can contain member declarations which comprise

  • constant declarations (see Section 6.4, p. 255)

  • method prototype declarations (see Section 6.4, p. 254)

  • nested class and interface declarations (see Section 7.1, p. 284)

An interface does not provide any implementation and is, therefore, abstract by definition. This means that it cannot be instantiated, but classes can implement it by providing implementations for its method prototypes. Declaring an interface abstract is superfluous and seldom done.

The member declarations can appear in any order in the interface body. Since interfaces are meant to be implemented by classes, interface members implicitly have public accessibility and the public modifier is omitted.

Interfaces with empty bodies are often used as markers to tag classes as having a certain property or behavior. Such interfaces are also called ability interfaces. Java APIs provide several examples of such marker interfaces: java.lang.Cloneable, java.io.Serializable, java.util.EventListener.

Method Prototype Declarations

An interface defines a contract by specifying a set of method prototypes, but no implementation. The methods in an interface are all implicitly abstract and public by virtue of their definition. A method prototype has the same syntax as an abstract method (see Section 4.10, p. 147). However, only the modifiers abstract and public are allowed, but these are invariably omitted.


<return type> <method name(<parameter list>) <throws clause>;

Example 6.9 declares two interfaces: IStack at (1) and ISafeStack at (5). These interfaces are discussed in the subsequent subsections.

Example 6.9 Interfaces
interface IStack {                                                // (1)
    void   push(Object item);
    Object pop();
}

class StackImpl implements IStack {                               // (2)
    protected Object[] stackArray;
    protected int      tos;  // top of stack

    public StackImpl(int capacity) {
        stackArray = new Object[capacity];
        tos        = -1;
    }

    public void push(Object item)                                 // (3)
        { stackArray[++tos] = item; }

    public Object pop() {                                         // (4)
        Object objRef = stackArray[tos];
        stackArray[tos] = null;
        tos--;
        return objRef;
    }

    public Object peek() { return stackArray[tos]; }
}

interface ISafeStack extends IStack {                             // (5)
    boolean isEmpty();
    boolean isFull();
}

class SafeStackImpl extends StackImpl implements ISafeStack {     // (6)

    public SafeStackImpl(int capacity) { super(capacity); }
    public boolean isEmpty() { return tos < 0; }                  // (7)
    public boolean isFull() { return tos >= stackArray.length-1; }// (8)
}
public class StackUser {

    public static void main(String[] args) {                      // (9)
        SafeStackImpl safeStackRef  = new SafeStackImpl(10);
        StackImpl     stackRef      = safeStackRef;
        ISafeStack    isafeStackRef = safeStackRef;
        IStack        istackRef     = safeStackRef;
        Object        objRef        = safeStackRef;

        safeStackRef.push("Dollars");                             // (10)
        stackRef.push("Kroner");
        System.out.println(isafeStackRef.pop());
        System.out.println(istackRef.pop());
        System.out.println(objRef.getClass());
    }
}

Output from the program:

Kroner
Dollars
class SafeStackImpl

Implementing Interfaces

Any class can elect to implement, wholly or partially, zero or more interfaces. A class specifies the interfaces it implements as a comma-separated list of unique interface names in an implements clause in the class header. The interface methods must all have public accessibility when implemented in the class (or its subclasses). A class can neither narrow the accessibility of an interface method nor specify new exceptions in the method's throws clause, as attempting to do so would amount to altering the interface's contract, which is illegal. The criteria for overriding methods also apply when implementing interface methods.

A class can provide implementations of methods declared in an interface, but it does not reap the benefits of interfaces unless the interface name is explicitly specified in its implements clause.

In Example 6.9, the class StackImpl implements the interface IStack by both specifying the interface name using the implements clause in its class header at (2) and providing the implementation for the methods in the interface at (3) and (4). Changing the public accessibility of these methods in the class will result in a compile-time error, as this would narrow their accessibility.

A class can choose to implement only some of the methods of its interfaces, (i.e., give a partial implementation of its interfaces). The class must then be declared as abstract (see Section 4.8, p. 134). Note that interface methods cannot be declared static, because they comprise the contract fulfilled by the objects of the class implementing the interface. Interface methods are always implemented as instance methods.

The interfaces a class implements and the classes it extends (directly or indirectly) are called supertypes of the class. Conversely, the class is a subtype of its supertypes. Classes implementing interfaces introduce multiple interface inheritance into their linear implementation inheritance hierarchy. However, note that regardless of how many interfaces a class implements directly or indirectly, it only provides a single implementation of a member that might have multiple declarations in the interfaces.

Extending Interfaces

An interface can extend other interfaces, using the extends clause. Unlike extending classes, an interface can extend several interfaces. The interfaces extended by an interface (directly or indirectly), are called superinterfaces. Conversely, the interface is a subinterface of its superinterfaces. Since interfaces define new reference types, superinterfaces and subinterfaces are also supertypes and subtypes, respectively.

A subinterface inherits all methods from its superinterfaces, as their method declarations are all implicitly public. A subinterface can override method prototype declarations from its superinterfaces. Overridden methods are not inherited. Method prototype declarations can also be overloaded, analogous to method overloading in classes.

Example 6.9 provides an example of multiple inheritance in Java. In Example 6.9, the interface ISafeStack extends the interface IStack at (5). The class SafeStackImpl both extends the StackImpl class and implements the ISafeStack interface at (6). Both the implementation and interface inheritance hierarchies for classes and interfaces defined in Example 6.9 are shown in Figure 6.3.

Figure 6.3. Inheritance Relations

graphics/06fig03.gif

In UML, an interface resembles a class. One way to differentiate between them is to use an «interface» stereotype as in Figure 6.3. Interface inheritance is shown similar to implementation inheritance, but with a dotted inheritance arrow. Thinking in terms of types, every reference type in Java is a subtype of Object type. This means that any interface type is also a subtype of Object type. We have augmented Figure 6.3 with an extra inheritance arrow to show this subtype relation.

It is instructive to note how class SafeStackImpl implements the ISafeStack interface: it inherits implementations of the push() and pop() methods from its superclass StackImpl, and provides its own implementation of the isFull() and isEmpty() methods from the ISafeStack interface. The interface ISafeStack inherits two method prototypes from its superinterface IStack. All its methods are implemented by the SafeStackImpl class. The class SafeStackImpl implicitly implements the IStack interface: it implements the ISafeStack interface that inherits from the IStack interface. This is readily evident from the diamond shape of the inheritance hierarchy in Figure 6.3. There is only one single implementation inheritance into the class SafeStackImpl.

Note that there are three different inheritance relations at work when defining inheritance among classes and interfaces:

  1. Linear implementation inheritance hierarchy between classes: a class extends another class (subclasses?superclasses).

  2. Multiple inheritance hierarchy between interfaces: an interface extends other interfaces (subinterfaces?superinterfaces).

  3. Multiple interface inheritance hierarchy between interfaces and classes: a class implements interfaces.

Although interfaces cannot be instantiated, references of an interface type can be declared. References to objects of a class can be assigned to references of the class' supertypes. In Example 6.9, an object of the class SafeStackImpl is created in the main() method of the class StackUser at (9). The reference value of the object is assigned to references of all the object's supertypes, which are used to manipulate the object. Polymorphic behavior of supertype references is discussed in Section 6.7.

Constants in Interfaces

An interface can also define named constants. Such constants are defined by field declarations and are considered to be public, static and final. These modifiers are usually omitted from the declaration. Such a constant must be initialized with an initializer expression (see Section 8.2, p. 331).

An interface constant can be accessed by any client (a class or interface) using its fully qualified name, regardless of whether the client extends or implements its interface. However, if a client is a class that implements this interface or an interface that extends this interface, then the client can also access such constants directly without using the fully qualified name. Such a client inherits the interface constants. Typical usage of constants in interfaces is illustrated in Example 6.10, showing both direct access and use of fully qualified names at (1) and (2), respectively.

Extending an interface that has constants is analogous to extending a class having static variables. In particular, these constants can be hidden by the subinterfaces. In the case of multiple inheritance of interface constants, any name conflicts can be resolved using fully qualified names for the constants involved.

Example 6.10 Variables in Interfaces
interface Constants {
    double PI_APPROXIMATION = 3.14;
    String AREA_UNITS = " sq.cm.";
    String LENGTH_UNITS = " cm.";
}

public class Client implements Constants {
    public static void main(String[] args) {
        double radius = 1.5;
        System.out.println("Area of circle is " +
                           (PI_APPROXIMATION*radius*radius) +
                           AREA_UNITS);             // (1) Direct access.
        System.out.println("Circumference of circle is " +
                           (2*Constants.PI_APPROXIMATION*radius) +
                           Constants.LENGTH_UNITS); // (2) Fully qualified name.
    }
}

Output from the program:

Area of circle is 7.0649999999999995 sq.cm.
Circumference of circle is 9.42 cm.