Interfaces
The
interface
keyword takes the abstract
concept
one step further. You could think of it as a “pure” abstract
class.
It allows the creator to establish the form for a class: method names, argument
lists and return types, but no method bodies. An
interface
can also contain data members of primitive types, but these are implicitly static
and final.
An
interface
provides only a form, but no implementation. An
interface
says: “This is what all classes that
implement
this particular interface will look like.” Thus, any code that uses a
particular
interface
knows what methods might be called for that
interface,
and that’s all. So the
interface
is used to establish a “protocol” between classes. (Some
object-oriented programming languages have a keyword called protocol
to do the same thing.)
To
create an
interface,
use the
interface
keyword instead of the
class
keyword. Like a class, you can add the public
keyword before the
interface
keyword
(but only if that
interface
is defined in a file of the same name) or leave it off to give “friendly”
status.
To
make a class that conforms to a particular
interface
(or group of
interfaces)
use the implements
keyword. You’re saying “The
interface
is what it looks like and here’s how it
works.”
Other than that, it bears a strong resemblance to inheritance. The diagram for
the instrument example shows this:
Once
you’ve implemented an
interface,
that implementation becomes an ordinary class that can be extended in the
regular way.
You
can choose to explicitly declare the method declarations in an
interface
as
public.
But they are
public
even if you don’t say it. So when you
implement
an
interface,
the methods from the
interface
must be defined as
public.
Otherwise they would default to “friendly” and you’d be
restricting the accessibility of a method during inheritance, which is not
allowed by the Java compiler.
You
can see this in the modified version of the
Instrument
example. Note that every method in the
interface
is strictly a declaration, which is the only thing the compiler allows. In
addition, none of the methods in
Instrument5
are declared as
public,
but they’re automatically
public
anyway:
//: Music5.java
// Interfaces
import java.util.*;
interface Instrument5 {
// Compile-time constant:
int i = 5; // static & final
// Cannot have method definitions:
void play(); // Automatically public
String what();
void adjust();
}
class Wind5 implements Instrument5 {
public void play() {
System.out.println("Wind5.play()");
}
public String what() { return "Wind5"; }
public void adjust() {}
}
class Percussion5 implements Instrument5 {
public void play() {
System.out.println("Percussion5.play()");
}
public String what() { return "Percussion5"; }
public void adjust() {}
}
class Stringed5 implements Instrument5 {
public void play() {
System.out.println("Stringed5.play()");
}
public String what() { return "Stringed5"; }
public void adjust() {}
}
class Brass5 extends Wind5 {
public void play() {
System.out.println("Brass5.play()");
}
public void adjust() {
System.out.println("Brass5.adjust()");
}
}
class Woodwind5 extends Wind5 {
public void play() {
System.out.println("Woodwind5.play()");
}
public String what() { return "Woodwind5"; }
}
public class Music5 {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument5 i) {
// ...
i.play();
}
static void tuneAll(Instrument5[] e) {
for(int i = 0; i < e.length; i++)
tune(e[i]);
}
public static void main(String[] args) {
Instrument5[] orchestra = new Instrument5[5];
int i = 0;
// Upcasting during addition to the array:
orchestra[i++] = new Wind5();
orchestra[i++] = new Percussion5();
orchestra[i++] = new Stringed5();
orchestra[i++] = new Brass5();
orchestra[i++] = new Woodwind5();
tuneAll(orchestra);
}
} ///:~
The
rest of the code works the same. It doesn’t matter if you are upcasting
to a “regular” class called
Instrument5,
an
abstract
class called
Instrument5,
or to an interface
called
Instrument5.
The behavior is the same. In fact, you can see in the
tune( )
method that there isn’t any evidence about whether
Instrument5
is a “regular” class, an
abstract
class or an
interface.
This is the intent: Each approach gives the programmer different control over
the way objects are created and used.
“Multiple
inheritance” in Java
The
interface
isn’t simply a “more pure” form of
abstract
class. It has a higher purpose than that. Because an
interface
has no implementation at all – that is, there is no storage associated
with an
interface
–
there’s
nothing to prevent many
interfaces
from being combined. This is valuable because there are times when you need to
say “An
x
is an
a
and
a
b
and
a
c.”
In C++, this act of combining multiple class interfaces is called multiple
inheritance
,
and it carries some rather sticky baggage because each class can have an
implementation. In Java, you can perform the same act, but only one of the
classes can have an implementation, so the problems seen in C++ do not occur
with Java when combining multiple interfaces:
In
a derived class, you aren’t forced to have a base class that is either an
abstract
or “concrete” (one with no
abstract
methods). If you
do
inherit from a non-
interface,
you
can inherit from only one. All the rest of the base elements must be
interfaces.
You place all the interface names after the
implements
keyword
and separate them with commas. You can have as many
interfaces
as you want and each one becomes an independent type that you can upcast to.
The following example shows a concrete class combined with several
interfaces
to produce a new class:
//: Adventure.java
// Multiple interfaces
import java.util.*;
interface CanFight {
void fight();
}
interface CanSwim {
void swim();
}
interface CanFly {
void fly();
}
class ActionCharacter {
public void fight() {}
}
class Hero extends ActionCharacter
implements CanFight, CanSwim, CanFly {
public void swim() {}
public void fly() {}
}
public class Adventure {
static void t(CanFight x) { x.fight(); }
static void u(CanSwim x) { x.swim(); }
static void v(CanFly x) { x.fly(); }
static void w(ActionCharacter x) { x.fight(); }
public static void main(String[] args) {
Hero i = new Hero();
t(i); // Treat it as a CanFight
u(i); // Treat it as a CanSwim
v(i); // Treat it as a CanFly
w(i); // Treat it as an ActionCharacter
}
} ///:~
You
can see that
Hero
combines the concrete class
ActionCharacter
with the interfaces
CanFight,
CanSwim,
and
CanFly.
When you combine a concrete class with interfaces this way, the concrete class
must come first, then the interfaces. (The compiler gives an error otherwise.)
Note
that the signature for
fight( )
is the same in the
interface
CanFight
and the class
ActionCharacter,
and that
fight( )
is
not
provided with a definition in
Hero.
The rule for an
interface
is that you can inherit from it (as you will see shortly), but then
you’ve got another
interface.
If you want to create an object of the new type, it must be a class with all
definitions provided. Even though
Hero
does not explicitly provide a definition for
fight( ),
the definition comes along with
ActionCharacter
so it is automatically provided and it’s possible to create objects of
Hero. In
class
Adventure,
you can see that there are four methods that take as arguments the various
interfaces and the concrete class. When a
Hero
object is created, it can be passed to any of these methods, which means it is
being upcast to each
interface
in turn. Because of the way interfaces are designed in Java, this works without
a hitch and without any particular effort on the part of the programmer.
Keep
in mind that the core reason for interfaces is shown in the above example: to
be able to upcast to more than one base type. However, a second reason for
using interfaces is the same as using an
abstract
base
class: to prevent the client programmer from making an object of this class and
to establish that it is only an interface. This brings up a question: Should
you use an interface
or an
abstract
class? An
interface
gives you the benefits of an
abstract
class
and
the benefits of an
interface,
so if it’s possible to create your base class without any method
definitions or member variables you should always prefer
interfaces
to
abstract
classes. In fact, if you know something is going to be a base class, your first
choice should be to make it an
interface,
and only if you’re forced to have method definitions or member variables
should you change to an
abstract
class.
Extending
an interface
with
inheritance
You
can easily add new method declarations to an interface
using inheritance, and you can also combine several
interfaces
into a new
interface
with inheritance. In both cases you get a new
interface,
as seen in this example:
//: HorrorShow.java
// Extending an interface with inheritance
interface Monster {
void menace();
}
interface DangerousMonster extends Monster {
void destroy();
}
interface Lethal {
void kill();
}
class DragonZilla implements DangerousMonster {
public void menace() {}
public void destroy() {}
}
interface Vampire
extends DangerousMonster, Lethal {
void drinkBlood();
}
class HorrorShow {
static void u(Monster b) { b.menace(); }
static void v(DangerousMonster d) {
d.menace();
d.destroy();
}
public static void main(String[] args) {
DragonZilla if2 = new DragonZilla();
u(if2);
v(if2);
}
} ///:~
DangerousMonster
is a simple extension to
Monster
that produces a new
interface.
This is implemented in
DragonZilla. The
syntax used in
Vampire
works
only
when inheriting interfaces. Normally, you can use extends
with only a single class, but since an
interface
can be made from multiple other interfaces,
extends
can refer to multiple base interfaces when building a new
interface.
As you can see, the
interface
names are simply separated with commas.
Grouping
constants
Because
any fields you put into an
interface
are automatically
static
and
final,
the
interface
is a convenient tool for creating
groups of constant values, much as you would with an
enum
in C or C++. For example:
//: Months.java
// Using interfaces to create groups of constants
package c07;
public interface Months {
int
JANUARY = 1, FEBRUARY = 2, MARCH = 3,
APRIL = 4, MAY = 5, JUNE = 6, JULY = 7,
AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,
NOVEMBER = 11, DECEMBER = 12;
} ///:~
Notice
the Java style of using all uppercase letters (with underscores to separate
multiple words in a single identifier) for
static
final
primitives that have constant initializers – that is, for compile-time
constants.
The
fields in an
interface
are
automatically
public,
so it’s unnecessary to specify that.
Now
you can use the constants from outside the package by importing
c07.*
or
c07.Months
just as you would with any other package, and referencing the values with
expressions like
Months.JANUARY.
Of course, what you get is just an
int
so there isn’t the extra type safety that C++’s
enum
has, but this (commonly-used) technique is certainly an improvement over
hard-coding numbers into your programs. (This is often referred to as using
“magic numbers” and it produces very difficult-to-maintain code.)
If
you do want extra type safety, you can build a class like this:
[28]
//: Month2.java
// A more robust enumeration system
package c07;
public final class Month2 {
private String name;
private Month2(String nm) { name = nm; }
public String toString() { return name; }
public final static Month2
JAN = new Month2("January"),
FEB = new Month2("February"),
MAR = new Month2("March"),
APR = new Month2("April"),
MAY = new Month2("May"),
JUN = new Month2("June"),
JUL = new Month2("July"),
AUG = new Month2("August"),
SEP = new Month2("September"),
OCT = new Month2("October"),
NOV = new Month2("November"),
DEC = new Month2("December");
public final static Month2[] month = {
JAN, JAN, FEB, MAR, APR, MAY, JUN,
JUL, AUG, SEP, OCT, NOV, DEC
};
public static void main(String[] args) {
Month2 m = Month2.JAN;
System.out.println(m);
m = Month2.month[12];
System.out.println(m);
System.out.println(m == Month2.DEC);
System.out.println(m.equals(Month2.DEC));
}
} ///:~
The
class is called
Month2
since there’s already a
Month
in the standard Java library. It’s a
final
class with a
private
constructor so no one can inherit from it or make any instances of it. The only
instances are the
final
static
ones created in the class itself:
JAN,
FEB,
MAR,
etc. These objects are also used in the array
month,
which lets you choose months by number instead of by name. (Notice the extra
JAN
in the array to provide an offset by one, so that December is month 12.) In
main( )
you can see the type
safety:
m
is a
Month2
object so it can be assigned only to a
Month2.
The previous example
Months.java
provided
only
int
values, so an
int
variable intended to represent a month could actually be given any integer
value, which wasn’t too safe.
This
approach also allows you to use
==
or
equals( )
interchangeably, as shown at the end of
main( ).
Initializing
fields in interfaces
Fields
defined in interfaces are automatically
static
and
final.
These cannot be “blank finals,” but they can be initialized with
non-constant expressions. For example:
//: RandVals.java
// Initializing interface fields with
// non-constant initializers
import java.util.*;
public interface RandVals {
int rint = (int)(Math.random() * 10);
long rlong = (long)(Math.random() * 10);
float rfloat = (float)(Math.random() * 10);
double rdouble = Math.random() * 10;
} ///:~
Since
the fields are
static,
they are initialized when the class is first loaded, upon first access of any
of the fields. Here’s a simple test:
//: TestRandVals.java
public class TestRandVals {
public static void main(String[] args) {
System.out.println(RandVals.rint);
System.out.println(RandVals.rlong);
System.out.println(RandVals.rfloat);
System.out.println(RandVals.rdouble);
}
} ///:~
The
fields, of course, are not part of the interface but instead are stored in the
static
storage area for that interface.
[28]
This approach was inspired by an e-mail from Rich Hoffarth.