The Java programming course
Lecture 4: Interfaces
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.
Classes can implement more than one interface. The Java programming language allows multiple inheritance of interface but only single inheritance of implementationa class can extend only one other class.
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.
- 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.
- 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.
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:
An interface declaration is similar to a class declaration, except that the keyword interface is used instead of class. 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. The natural ordering for points could be their distance from the origin. We could then make Point objects Comparable:
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.
Interfaces introduce type names just as classes do, so you can declare variables of those types. For example:
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.
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.
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.
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.
Extending Interfaces
Interfaces can be extended using the extends keyword. Interfaces, unlike classes, can extend more than one other interface:
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.
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. 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 namethe usual way of referring to static members.
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.
When a class implements Y you can access the constants in Y as though they were constants declared in the class. For example, given
you can do
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:
which prints out
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.
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 effectthe 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:
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.
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.
Working with Interfaces
The previous lecture 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:
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. In effect, the Attributed interface defines a collection typea set of attributesso 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.
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.
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:
The initializer for attrTable creates a HashMap object to hold attributes. 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:
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.
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 interfaceit has neither methods nor constants, but marks a class as partaking in the cloning mechanism.
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 interface java.io.Serializable is marker interface, as are both java.rmi.Remote and java.util.EventListener.
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.
References
- K.Arnold, J.Gosling, D.Holmes: The Java programming language. Fourth edition. Section 4: interfaces.