Implementing Interfaces – Object-Oriented Programming

Implementing Interfaces – Object-Oriented Programming

Implementing Interfaces

A class can implement, wholly or partially, any number of 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. Implementing an interface essentially means that the class must provide implementation for the abstract methods declared in the interface. In fact, an abstract method declaration is overridden when an implementation is provided by a class. Optionally, the class can also override any default methods if necessary.

The criteria for overriding methods in classes also apply when implementing abstract and any default methods from interfaces. A class must provide a method implementation with the same method signature and (covariant) return type as the declaration in the interface, and it can neither narrow the public access of the 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.

Best practice advocates using the @Override annotation on the implementations of abstract and default methods (Example 5.10). The compiler checks that such a method satisfies the criteria for overriding another method. This check ensures that the method is not inadvertently overloaded by catching the error at compile time. The annotations also aid in the readability of the code, making obvious which methods are overridden.

It is not enough for a class to provide implementations of methods declared in an interface. The class must also specify the interface name in its implements clause in order to reap the benefits of interfaces.

In Example 5.10, the class Stack at (2) implements the interface IStack. It both specifies the interface name using the implements clause in its class header at (2) and provides the implementation for the abstract methods in the interface at (3) and (4). Changing the public access of these methods in the class will result in a compile-time error, as this would narrow their accessibility.

Example 5.10 Implementing Interfaces

Click here to view code image

// File: StackUser.java
interface IStack {                                                        // (1)
  void   push(Object item);
  Object pop();
}
//______________________________________________________________________________
class Stack implements IStack {                                           // (2)
  protected Object[] elements;
  protected int      tos;  // top of stack

  public Stack(int capacity) {
    elements = new Object[capacity];
    tos        = -1;
  }
  @Override
  public void push(Object item) { elements[++tos] = item; }               // (3)
  @Override
  public Object pop() {                                                   // (4)
    Object objRef = elements[tos];
    elements[tos] = null;
    tos–;
    return objRef;
  }
  public Object peek() { return elements[tos]; }
}
//______________________________________________________________________________
interface ISafeStack extends IStack {                                     // (5)
  boolean isEmpty();
  boolean isFull();
}
//______________________________________________________________________________
class SafeStack extends Stack implements ISafeStack {                     // (6)

  public SafeStack(int capacity) { super(capacity); }
  @Override public boolean isEmpty() { return tos < 0; }                  // (7)
  @Override public boolean isFull()  { return tos >= elements.length-1; } // (8)
}
//______________________________________________________________________________
public class StackUser {

  public static void main(String[] args) {                                // (9)
    SafeStack  safeStackRef  = new SafeStack(10);
    Stack      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 SafeStack

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

The interfaces that a class implements and the classes that it extends (directly or indirectly) are called supertypes of the class. Conversely, the class is a subtype of its supertypes. A class can now inherit from multiple interfaces. Even so, regardless of how many interfaces a class implements directly or indirectly, it provides just a single implementation of any abstract method declared in multiple interfaces.

Single implementation of an abstract method is illustrated by the following code, where the Worker class at (5) provides only one implementation of the doIt() method that is declared in both interfaces, at (1) and (2). The class Worker fulfills the contract for both interfaces, as the doIt() method declarations at (1) and (2) have the same method signature and return type. However, the class Combined at (3) declares that it implements the two interfaces, but does not provide any implementation of the doIt() method; consequently, it must be declared as abstract.

Click here to view code image

interface IA { int doIt(); }                      // (1)
interface IB { int doIt(); }                      // (2)
abstract class Combined implements IA, IB { }     // (3)
public class Worker implements IA, IB {           // (4)
  @Override
  public int doIt() { return 0; }                 // (5)
}

Multiple declarations of the same abstract method in several interfaces implemented by a class do not result in multiple inheritance of implementation, as no implementation is inherited for such methods. The abstract method doIt() has only one implementation in the Worker class. Multiple inheritance of type is also not a problem, as the class is a subtype of all interfaces it implements.

If the doIt() methods in the two previous interfaces at (1) and (2) had the same signatures but different return types, the Worker class would not be able to implement both interfaces. This is illustrated by the code below. The doIt() methods at (1) and (2) have the same signature, but different return types. The ChallengedWorker class provides two implementations of the doIt() method at (5) and (6), which results in compile-time errors because a class cannot have two methods with the same signature but different return types. Removing either implementation from the ChallengedWorker class will be flagged as a compile-time error because the ChallengedWorker class will not be implementing both interfaces. There is no way the ChallengedWorker class can implement both interfaces, given the declarations shown in the code. In addition, the abstract class Combined at (3) will not compile because it will be inheriting two methods with conflicting abstract method declarations. In fact, the compiler complains of duplicate methods.

Click here to view code image

interface IA { int doIt(); }                          // (1)
interface IB { double doIt(); }                       // (2)
abstract class Combined implements IA, IB { }         // (3) Compile-time error!
public class ChallengedWorker implements IA, IB {     // (4)
  @Override
  public int doIt() { return 0; }                     // (5) Compile-time error!
  @Override
  public double doIt() {                              // (6) Compile-time error!
    System.out.println(“Sorry!”);
    return = 0.0;
  }
}

An enum type (p. 298) can also implement interfaces. The discussion above on classes implementing interfaces also applies to enum types, but with the following caveat. Since an enum type can never be abstract, each abstract method of its declared interfaces must be implemented as an instance member either in the enum type or in all constant-specific class bodies defined in the enum type— otherwise, the code will not compile.