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
();
Both Automobile
and Lawnmower
implement Driveable
, so they can be considered
interchangeable objects of that type.
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.
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
);
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.
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.
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.