An interface encapsulates a coherent set of services and attributes (broadly, a Role), without explicitly binding this functionality to that of any particular object or code.
Interfaces are ``more abstract'' than classes since they don't say
anything at all about representation or code. All they do is describe
public operations. For example, the java.lang.Runnable
interface is just:
public interface Runnable { public abstract void run(); }Notes:
abstract
means that no code
is supplied for this method. It is strictly optional
to bother listing it in interfaces
. All
methods defined in interfaces
must be abstract
.
interfaces
must
be public
.
interface
is to list public static final
constants.
Interfaces turn out to be a more useful concept than they might first appear. They are vital tools for stepping up from writing occasional one-shot classes to writing extensible packages and frameworks of classes that form the basis for suites of applications:
Runnable
have
nothing else in common. Or for a different kind of example,
linked-lists, hash tables, trees, and many other data structures
all support basic operations like addElement
, so
can support a common interface even though their internal
structures are completely different (See the
collections package for several examples along these lines.)
Interfaces are otherwise used pretty much just like classes in Java. For example, you can write a method that accepts any object obeying a stated interface and invoke methods on it, as in:
class Foo { void doIt(Runnable r) { r.run(); } }
Simple one-method interfaces are often needed to standardize naming
conventions. The Runnable
interface, and its
run()
method, used for argumentless, resultless actions
is so common to be predefined in Java. While not predefined, other
canonical forms also have commonly adopted names. These include:
public interface Procedure { // apply some operation to a single object public void apply(Object x); } public interface Generator { // return an object public Object next(); } public interface Function { // apply an operation; return an object public Object map(Object x); } public interface Predicate { // test some property of an object public boolean test(Object x); } public interface Combiner { // return result of a binary operation public Object combine(Object x, Object y); }
But in Java, each subinterface must be explicitly listed
as extend
ing one or more other interfaces, implicitly
inheriting all of the super-interface operations. For a classic
example:
interface File { public void open(String name); public void close(); } interface ReadableFile extends File { public byte readByte(); } interface WritableFile extends File { public void writeByte(byte b); } interface ReadWriteFile extends ReadableFile, WritableFile { public void seek(int position); }
It is common when defining interface hierarchies to use fairly fine-grained interfaces at the top, each defining only a few operations, and to use (multiple) interface inheritance to define ``fatter'', more useful ones as various combinations of these basic bits of functionality.
For example, to implement Runnable in the dumbest (but fastest!) possible way:
class FastRunner implements Runnable { public void run() {} }
Note that there's a difference between a method defined not to do
anything (i.e., with a null body) versus an abstract
method, which is not defined at all.
Subclasses can also implement unrelated interfaces. For
example, to create a subclass of some pre-exisiting class
that also implements Runnable
to execute a particular
method when run()
is invoked:
class URLReader { protected String url_; protected byte[] buffer_; public URLReader(String url, byte[] buffer) { url_ = url; buffer_ = buffer; } public void read() { /* read into buffer */ } } class RunnableURLReader extends URLReader implements Runnable { public RunnableURLReader(String url, byte[] buffer) { super(url, buffer); } public void run() { read(); } }
abstract
methods in addition to normal methods and
instance variables. Such classes must be explicitly declared
as abstract
. For example:
abstract class MachinePart { protected String partID_; public String partID() { return partID_; } protected MachinePart(String id) { partID_ = id; } public abstract boolean canConnectTo(MachinePart other); }
Here, the MachinePart
class set down a common
representation for partIDs
that holds for all subclasses.
But the declaration of canConnectTo
is abstract since it
has to be implemented differently in every public subclass.
(Alternatively, we could have provided a default implementation, say
to return false
.)
Every MachinePart
subclass gets its partID
mechanics for free, but must specially implement canConnectTo
.
And as usual, it can further extend the class or even implement
additional interfaces:
class Gadget extends MachinePart { public Gadget(String id) { super(id); } public boolean canConnectTo(MachinePart other) { if (other instanceof Gadget) return true; else if (other.partID().startsWith("Universal")) return true; else return false; } public void specialGadgetMethod() { ... } }
Abstract classes are like interfaces in one other sense: Neither
are directly instantiable via new
. So you cannot write
code like:
Runnable r = new Runnable(); // wrong MachinePart p = new MachinePart(); // wrong
The reason they are not instantiable is that there is no code supporting one or more methods, so no such object can be constructed. This is true even though abstract classes often have constructors. But these are called only from subclass constructors.
There are several further variations on this interplay between
interfaces and classes. For example, it is legal in Java to say that
an abstract
class implements
an
interface
even though it doesn't actually define one or
more of the methods, but instead leaves them for its own subclasses to
define. Such odd-sounding techniques tend to become standard
tricks of the trade when designing large frameworks and packages.
A factory class exists just to help create instances of other classes. Factories often have methods that produce instances of several related classes, but all in a compatible way. Factories should themselves be defined via interfaces, so the client need not know which particular factory object it is using. Ideally, all such matters can be reduced to a single call to construct the appropriate ``master'' factory in a client application.
Among the most well-known examples of factories are UI toolkits that are designed to run on different windowing systems. There might be interfaces for things like:
interface ScrollBar { ... } interface MenuBar { ... } ...
And associated classes implementing them on different windowing systems:
class MotifScrollBar implements ScrollBar { ... } class Win95ScrollBar implements ScrollBar { ... } ...
And a factory interface that also doesn't commit to representation:
interface Factory { public abstract ScrollBar newScrollBar(); public abstract MenuBar newMenuBar(); ... }
But implementation classes that do:
class MotifFactory implements Factory { public ScrollBar newScrollBar() { return new MotifScrollBar(...); } ... }
Finally, to get an application up and running on different systems, only one bit of implementation-dependent code is needed:
class App { public static void main(String args[]) { Factory wf = null; if (args[0].equals("Motif")) wf = new MotifFactory(); else ... startApp(wf); } }
All other objects construct widgets using the factory passed around (or, more likely, kept in a central data structure), never knowing or caring what windowing system they happen to be running on.
Note: The Java AWT uses a strategy that is similar in concept but different in detail for achieving implementation independence, via a set of ``peer'' implementation classes that are specialized for the platform that the Java session is being run on, and instantiated when user-level AWT objects are constructed.