Interfaces

Java expands on the concept of abstract methods with interfaces. It’s often desirable to specify a group of abstract methods defining some behavior for an object without tying it to any implementation at all. In Java, this is called an interface. An interface defines a set of methods that a class must implement. A class in Java can declare that it implements an interface if it implements the required methods. Unlike extending an abstract class, a class implementing an interface doesn’t have to inherit from any particular part of the inheritance hierarchy or use a particular implementation.

Interfaces are kind of like Boy Scout or Girl Scout merit badges. A scout who has learned to build a birdhouse can walk around wearing a little sleeve patch with a picture of one. This says to the world, “I know how to build a birdhouse.” Similarly, an interface is a list of methods that define some set of behavior for an object. Any class that implements each method listed in the interface can declare at compile time that it implements the interface and wear, as its merit badge, an extra type—the interface’s type.

Interface types act like class types. You can declare variables to be of an interface type, you can declare arguments of methods to accept interface types, and you can specify that the return type of a method is an interface type. In each case, what is meant is that any object that implements the interface (i.e., wears the right merit badge) can fill that role. In this sense, interfaces are orthogonal to the class hierarchy. They cut across the boundaries of what kind of object an item is and deal with it only in terms of what it can do. A class can implement as many interfaces as it desires. In this way, interfaces in Java replace much of the need for multiple inheritance in other languages (and all its messy complications).

An interface looks, essentially, like a purely abstract class (i.e., a class with only abstract methods). You define an interface with the interface keyword and list its methods with no bodies, just prototypes (signatures):

    interface Driveable {
        boolean startEngine();
        void stopEngine();
        float accelerate( float acc );
        boolean turn( Direction dir );
    }

The previous example defines an interface called Driveable with four methods. It’s acceptable, but not necessary, to declare the methods in an interface with the abstract modifier; we haven’t done that here. More importantly, the methods of an interface are always considered public, and you can optionally declare them as so. Why public? Well, the user of the interface wouldn’t necessarily be able to see them otherwise, and interfaces are generally intended to describe the behavior of an object, not its implementation.

Interfaces define capabilities, so it’s common to name interfaces after their capabilities. Driveable, Runnable, and Updateable are good interface names. Any class that implements all the methods can then declare that it implements the interface by using a special implements clause in its class definition. For example:

    class Automobile implements Driveable {
        ...
        public boolean startEngine() {
            if ( notTooCold )
                engineRunning = true;
            ...
        }

        public void stopEngine() {
            engineRunning = false;
        }

        public float accelerate( float acc ) {
            ...
        }

        public boolean turn( Direction dir ) {
            ...
        }
        ...
    }

Here, the class Automobile implements the methods of the Driveable interface and declares itself a type of Driveable using the implements keyword.

As shown in Figure 6-5, another class, such as Lawnmower, can also implement the Driveable interface. The figure illustrates the Driveable interface being implemented by two different classes. While it’s possible that both Automobile and Lawnmower could derive from some primitive kind of vehicle, they don’t have to in this scenario.

After declaring the interface, we have a new type, Driveable. We can declare variables of type Driveable and assign them any instance of a Driveable object:

    Automobile auto = new Automobile();
    Lawnmower mower = new Lawnmower();
    Driveable vehicle;

    vehicle = auto;
    vehicle.startEngine();
    vehicle.stopEngine();

    vehicle = mower;
    vehicle.startEngine();
    vehicle.stopEngine();
Implementing the Driveable interface

Figure 6-5. Implementing the Driveable interface

Both Automobile and Lawnmower implement Driveable, so they can be considered interchangeable objects of that type.

Interfaces as Callbacks

Interfaces can be used to implement “callbacks” in Java. This is when an object effectively passes a reference to one or more of its methods to another object. The callback occurs when the called object subsequently invokes one of the methods. In C or C++, this is prime territory for function pointers; Java uses interfaces instead. More generally, this concept is extended in Java to the concept of events in which listener objects register with event sources. We’ll cover events in great detail in later chapters.

Consider two classes: a TickerTape class that displays data and a TextSource class that provides an information feed. We’d like our TextSource to send any new text data. We could have TextSource store a reference to a TickerTape object, but then we could never use our TextSource to send data to any other kind of object. Instead, we’d have to proliferate subclasses of TextSource that dealt with different types. A more elegant solution is to have TextSource store a reference to an interface type, TextReceiver:

    interface TextReceiver {
        void receiveText( String text );
    }

    class TickerTape implements TextReceiver {
        public void receiveText( String text ) {
            System.out.println("TICKER:\n" + text + "\n");
        }
    }

    class TextSource {
        TextReceiver receiver;

        TextSource( TextReceiver r ) {
            receiver = r;
        }

        public void sendText( String s ) {
            receiver.receiveText( s );
        }
    }

The only thing TextSource really cares about is finding the right method to invoke in order to output some text. Using an interface establishes a “contract,” receiveText(), for that method.

When the TextSource is constructed, a reference to the TickerTape (which implements the interface) is stored in an instance variable. This “registers” the TickerTape as the TextSource’s “output device.” Whenever it needs to output data, the TextSource calls the output device’s receiveText() method. Later, we’ll see that many APIs in Java use a model like this, but more often many “receivers” may register with the same source.

Interface Variables

Although interfaces mostly allow us to specify behavior without implementation, there’s one exception. An interface can contain constants (static final variables ), which can be referred to directly through the interface name, and which also appear in any class that implements the interface. This feature allows constants to be packaged for use with the methods of the interface:

    interface Scaleable {
        static final int BIG = 0, MEDIUM = 1, SMALL = 2;
        void setScale( int size );
    }

The Scaleable interface defines three integers: BIG, MEDIUM, and SMALL. All variables defined in interfaces are implicitly final and static; you don’t need to use the modifiers, but for clarity, we recommend that you do. A class that implements Scaleable sees these constants:

    class Box implements Scaleable {

        void setScale( int size ) {
            switch( size ) {
                case BIG:
                    ...
                case MEDIUM:
                    ...
                case SMALL:
                    ...
            }
        }
        ...
    }

While there is nothing technically wrong with using interfaces in this way, the main incentive for doing so disappeared when Java added enumerations and static imports. Using interfaces for this purpose is bad because all those public, static constants then appear in the public API of your class and can confuse those who use it. What’s worse, you can’t remove them later because other code may rely on the class that contains those values. It’s better to use an enumeration or to put your constants in their own class and then use the new static import syntax to remove the hassle of referring to them. We’ll discuss static import later in this chapter. This code snippet gives a glimpse of how it works:

    enum SizeConstants { BIG, MEDIUM, SMALL }

    // usage
    static import mypackage.SizeConstants;
    ...
    setSize( MEDIUM );

Flag interfaces

Sometimes completely empty interfaces serve as a marker that a class has a special property. The java.io.Serializeable interface is a good example. Classes that implement Serializeable don’t have to add any methods or variables. Their additional type simply identifies them to Java as classes that want to be able to be serialized. This usage of interfaces is less important now that Java has annotations, described in Chapter 7.

Subinterfaces

An interface can extend another interface, just as a class can extend another class. Such an interface is called a subinterface. For example:

    interface DynamicallyScaleable extends Scaleable {
        void changeScale( int size );
    }

The interface DynamicallyScaleable extends our previous Scaleable interface and adds an additional method. A class that implements DynamicallyScaleable must implement all the methods of both interfaces.

Note here that we are using the term extends and not implements to subtype the interface. Interfaces can’t implement anything! But an interface is allowed to extend as many interfaces as it wants. If you want to extend two or more interfaces, list them after the extends keyword, separated by commas:

    interface DynamicallyScaleable extends Scaleable, SomethingElseable {
        ...
    }

A class that implements this interface must also implement the other interfaces. Furthermore, interface subtypes are assignable to their supertypes in the same way that classes are, so an instance of DynamicallyScaleable can be assigned to a variable of type Scaleable, as you might expect.

Overlapping and conflicting methods

We should also note the possibility that when an interface extends two or more interfaces (or when a class implements two or more interfaces), there may be overlapping or conflicting methods in those interfaces. If two methods in different interfaces have exactly the same signature and return type, there is no problem and the implementation in the class satisfies both interfaces. If the methods differ in the way that overloaded methods do, the class must implement both method signatures. If the methods have the same name but differ in return or exception types, the class cannot implement both and compile-time errors occur.

Get Learning Java, 4th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.