Team LiB
Previous Section Next Section

3.9. Interfaces

Like a class, an interface defines a new reference type. Unlike classes, however, interfaces provide no implementation for the types they define. As its name implies, an interface specifies only an API: all of its methods are abstract and have no bodies. It is not possible to directly instantiate an interface and create a member of the interface type. Instead, a class must implement the interface to provide the necessary method bodies. Any instances of that class are members of both the type defined by the class and the type defined by the interface. Interfaces provide a limited but very powerful alternative to multiple inheritance .[9] Classes in Java can inherit members from only a single superclass, but they can implement any number of interfaces. Objects that do not share the same class or superclass may still be members of the same type by virtue of implementing the same interface.

[9] C++ supports multiple inheritance, but the ability of a class to have more than one superclass adds a lot of complexity to the language.

3.9.1. Defining an Interface

An interface definition is much like a class definition in which all the methods are abstract and the keyword class has been replaced with interface. For example, the following code shows the definition of an interface named Centered. A Shape class, such as those defined earlier in the chapter, might implement this interface if it wants to allow the coordinates of its center to be set and queried:

public interface Centered {
    void setCenter(double x, double y);
    double getCenterX();
    double getCenterY();
}

A number of restrictions apply to the members of an interface:

  • An interface contains no implementation whatsoever. All methods of an interface are implicitly abstract and must have a semicolon in place of a method body. The abstract modifier is allowed but, by convention, is usually omitted. Since static methods may not be abstract, the methods of an interface may not be declared static.

  • An interface defines a public API. All members of an interface are implicitly public, and it is conventional to omit the unnecessary public modifier. It is an error to define a protected or private method in an interface.

  • An interface may not define any instance fields. Fields are an implementation detail, and an interface is a pure specification without any implementation. The only fields allowed in an interface definition are constants that are declared both static and final.

  • An interface cannot be instantiated, so it does not define a constructor.

  • Interfaces may contain nested types. Any such types are implicitly public and static. See Section 3.10 later in this chapter.

3.9.1.1 Extending interfaces

Interfaces may extend other interfaces, and, like a class definition, an interface definition may include an extends clause. When one interface extends another, it inherits all the abstract methods and constants of its superinterface and can define new abstract methods and constants. Unlike classes, however, the extends clause of an interface definition may include more than one superinterface. For example, here are some interfaces that extend other interfaces:

public interface Positionable extends Centered {
    void setUpperRightCorner(double x, double y);
    double getUpperRightX();
    double getUpperRightY();
}
public interface Transformable extends Scalable, Translatable, Rotatable {}
public interface SuperShape extends Positionable, Transformable {}

An interface that extends more than one interface inherits all the abstract methods and constants from each of those interfaces and can define its own additional abstract methods and constants. A class that implements such an interface must implement the abstract methods defined directly by the interface, as well as all the abstract methods inherited from all the superinterfaces.

3.9.2. Implementing an Interface

Just as a class uses extends to specify its superclass, it can use implements to name one or more interfaces it supports. implements is a Java keyword that can appear in a class declaration following the extends clause. implements should be followed by a comma-separated list of interfaces that the class implements.

When a class declares an interface in its implements clause, it is saying that it provides an implementation (i.e., a body) for each method of that interface. If a class implements an interface but does not provide an implementation for every interface method, it inherits those unimplemented abstract methods from the interface and must itself be declared abstract. If a class implements more than one interface, it must implement every method of each interface it implements (or be declared abstract).

The following code shows how we can define a CenteredRectangle class that extends the Rectangle class from earlier in the chapter and implements our Centered interface.

public class CenteredRectangle extends Rectangle implements Centered {
  // New instance fields
  private double cx, cy;

  // A constructor
  public CenteredRectangle(double cx, double cy, double w, double h) { 
    super(w, h); 
    this.cx = cx;
    this.cy = cy;
  }

  // We inherit all the methods of Rectangle but must
  // provide implementations of all the Centered methods. 
  public void setCenter(double x, double y) { cx = x; cy = y; }
  public double getCenterX() { return cx; }
  public double getCenterY() { return cy; }
}

Suppose we implement CenteredCircle and CenteredSquare just as we have implemented this CenteredRectangle class. Since each class extends Shape, instances of the classes can be treated as instances of the Shape class, as we saw earlier. Since each class implements the Centered interface, instances can also be treated as instances of that type. The following code demonstrates how objects can be members of both a class type and an interface type:

Shape[] shapes = new Shape[3];          // Create an array to hold shapes

// Create some centered shapes, and store them in the Shape[]
// No cast necessary: these are all widening conversions
shapes[0] = new CenteredCircle(1.0, 1.0, 1.0);
shapes[1] = new CenteredSquare(2.5, 2, 3);
shapes[2] = new CenteredRectangle(2.3, 4.5, 3, 4);

// Compute average area of the shapes and average distance from the origin
double totalArea = 0;
double totalDistance = 0;
for(int i = 0; i < shapes.length; i++) {
  totalArea += shapes[i].area();       // Compute the area of the shapes
  if (shapes[i] instanceof Centered) { // The shape is a Centered shape
    // Note the required cast from Shape to Centered (no cast would
    // be required to go from CenteredSquare to Centered, however). 
    Centered c = (Centered) shapes[i]; // Assign it to a Centered variable
    double cx = c.getCenterX();        // Get coordinates of the center
    double cy = c.getCenterY();        // Compute distance from origin
    totalDistance += Math.sqrt(cx*cx + cy*cy);
  }
}
System.out.println("Average area: " + totalArea/shapes.length);
System.out.println("Average distance: " + totalDistance/shapes.length);

This example demonstrates that interfaces are data types in Java, just like classes. When a class implements an interface, instances of that class can be assigned to variables of the interface type. Don't interpret this example to imply that you must assign a CenteredRectangle object to a Centered variable before you can invoke the setCenter( ) method or to a Shape variable before you can invoke the area() method. CenteredRectangle defines setCenter( ) and inherits area() from its Rectangle superclass, so you can always invoke these methods.

3.9.2.1 Implementing multiple interfaces

Suppose we want shape objects that can be positioned in terms of not only their center points but also their upper-right corners. And suppose we also want shapes that can be scaled larger and smaller. Remember that although a class can extend only a single superclass, it can implement any number of interfaces. Assuming we have defined appropriate UpperRightCornered and Scalable interfaces, we can declare a class as follows:

public class SuperDuperSquare extends Shape
    implements Centered, UpperRightCornered, Scalable {
   // Class members omitted here 
}

When a class implements more than one interface, it simply means that it must provide implementations for all abstract methods in all its interfaces.

3.9.3. Interfaces vs. Abstract Classes

When defining an abstract type (e.g., Shape) that you expect to have many subtypes (e.g., Circle, Rectangle, Square), you are often faced with a choice between interfaces and abstract classes. Since they have similar features, it is not always clear which to use.

An interface is useful because any class can implement it, even if that class extends some entirely unrelated superclass. But an interface is a pure API specification and contains no implementation. If an interface has numerous methods, it can become tedious to implement the methods over and over, especially when much of the implementation is duplicated by each implementing class.

An abstract class does not need to be entirely abstract; it can contain a partial implementation that subclasses can take advantage of. In some cases, numerous subclasses can rely on default method implementations provided by an abstract class. But a class that extends an abstract class cannot extend any other class, which can cause design difficulties in some situations.

Another important difference between interfaces and abstract classes has to do with compatibility. If you define an interface as part of a public API and then later add a new method to the interface, you break any classes that implemented the previous version of the interface. If you use an abstract class, however, you can safely add nonabstract methods to that class without requiring modifications to existing classes that extend the abstract class.

In some situations, it is clear that an interface or an abstract class is the right design choice. In other cases, a common design pattern is to use both. Define the type as a totally abstract interface, then create an abstract class that implements the interface and provides useful default implementations that subclasses can take advantage of. For example:

// Here is a basic interface. It represents a shape that fits inside
// of a rectangular bounding box. Any class that wants to serve as a 
// RectangularShape can implement these methods from scratch. 
public interface RectangularShape {
    void setSize(double width, double height);
    void setPosition(double x, double y);
    void translate(double dx, double dy);
    double area();
    boolean isInside();
}

// Here is a partial implementation of that interface. Many
// implementations may find this a useful starting point. 
public abstract class AbstractRectangularShape implements RectangularShape {
    // The position and size of the shape
    protected double x, y, w, h;

    // Default implementations of some of the interface methods
    public void setSize(double width, double height) { w = width; h = height; }
    public void setPosition(double x, double y) { this.x = x; this.y = y; }
    public void translate (double dx, double dy) { x += dx; y += dy; }
}

3.9.4. Marker Interfaces

Sometimes it is useful to define an interface that is entirely empty. A class can implement this interface simply by naming it in its implements clause without having to implement any methods. In this case, any instances of the class become valid instances of the interface. Java code can check whether an object is an instance of the interface using the instanceof operator, so this technique is a useful way to provide additional information about an object.

The java.io.Serializable interface is a marker interface of this sort. A class implements Serializable interface to tell ObjectOutputStream that its instances may safely be serialized. java.util.RandomAccess is another example: java.util.List implementations implement this interface to advertise that they provide fast random access to the elements of the list. ArrayList implements RandomAccess, for example, while LinkedList does not. Algorithms that care about the performance of random-access operations can test for RandomAccess like this:

// Before sorting the elements of a long arbitrary list, we may want to make
// sure that the list allows fast random access.  If not, it may be quicker
// make a random-access copy of the list before sorting it.
// Note that this is not necessary when using java.util.Collections.sort().
List l = ...;  // Some arbitrary list we're given
if (l.size() > 2 && !(l instanceof RandomAccess))  l = new ArrayList(l);
sortListInPlace(l);

3.9.5. Interfaces and Constants

As noted earlier, constants can appear in an interface definition. Any class that implements an interface inherits the constants it defines and can use them as if they were defined directly in the class itself. Importantly, there is no need to prefix the constants with the name of the interface or provide any kind of implementation of the constants.

When a set of constants is used by more than one class, it is tempting to define the constants once in an interface and then have any classes that require the constants implement the interface. This situation might arise, for example, when client and server classes implement a network protocol whose details (such as the port number to connect to and listen on) are captured in a set of symbolic constants. As a concrete example, consider the java.io.ObjectStreamConstants interface, which defines constants for the object serialization protocol and is implemented by both ObjectInputStream and ObjectOutputStream.

The primary benefit of inheriting constant definitions from an interface is that it saves typing: you don't need to specify the type that defines the constants. Despite its use with ObjectStreamConstants, this is not a recommended technique. The use of constants is an implementation detail that is not appropriate to declare in the implements clause of a class signature.

A better approach is to define constants in a class and use the constants by typing the full class name and the constant name. In Java 5.0 and later, you can save typing by importing the constants from their defining class with the import static declaration. See Section 2.10 in Chapter 2 for details.

    Team LiB
    Previous Section Next Section