Java interfaces #
A Java interface is a programming interface with additional (object-oriented) features.
Syntactically, a Java interface is similar to a class. But (in its simplest form), it contains no code. Instead, it specifies the behavior of some methods (which are implemented in some classes).
A Java interface can be used to specify how two components of a system communicate (before the interface is even implemented!).
Examples #
The implementation of our game contains an interface called DisplayManager
.
This interface specifies how the backend can share with the frontend information to be drawn
on screen.
public interface DisplayManager {
...
/**
* Displays the input snapshot on screen, as well as the input message.
*/
void drawSnapshot(Snapshot snapshot, String message);
...
}
Note. This description contains no code.
The method above takes as argument an object of type Snapshot
.
A snapshot intuitively contains all the information needed by the fronted to draw the current state of the game on screen (board, active player, stats, etc.).
Snapshot
is itself an interface, which specifies (among others) a method called getSizeOfReinforcement
, as follows:
public interface Snapshot {
...
/**
* Returns the number of units that will enter the board if reinforcement
* is called for the input player.
*/
int getSizeOfReinforcement(Player player);
...
}
Note. The name of an interface can be used as a reference type (similarly to the name of a class). For instance,
Snaphost
is used above as the type of the first argument of the methoddrawSnapshot
.
A Java interface acts not only as documentation, but also as a contract : any (non-abstract) class that implements this interface must implement all methods declared in the interface (otherwise the program will not compile).
So a (non-abstract) class that implements the interface Snapshot
must implement the method getSizeOfReinforcement
(or inherit an implementation of this method).
The interface Snapshot
specifies another method
Board getBoard();
which must return an object of type Board
.
And Board
is itself an interface, which specifies the information needed to draw the game board (units, position, etc).
Here is for instance a fragment of the Board
interface:
/**
* Board for an ongoing game.
* This is a two-dimensional grid.
*
* Tile coordinates are natural numbers and start at 0.
* The top left tile has coordinates (0,0).
*/
public interface Board {
...
/**
* Return the maximum possible index for a column (a.k.a. number of columns -1)
*/
int getMaxColumnIndex();
/**
* Returns the maximum possible index for a row (a.k.a. number of rows -1)
*/
int getMaxRowIndex();
/**
* Returns the unit standing on the tile at the input coordinates, if any.
* Throws an exception if the coordinates are out of this board's boundaries.
*/
Optional<Unit> getUnit(int rowIndex, int columnIndex) throws CoordinatesOutOfBoardException;
...
}
Observe that these interfaces only specify what classes should implement, not how. In other words, these interfaces specifies communication between components, without exposing unnecessary implementation details.
When to use a Java interface? #
Technically, Java interfaces are not needed in a project. But they can make collaboration within a team of developers significantly easier. In particular:
-
An interface is a convenient way to expose what is needed to interact with your code, while hiding the details of your implementation. For instance, the method
drawSnapshot
(mentioned above) can be called to draw a snapshot on screen, without knowing how this method is implemented. -
As a contract, an interface allows two programmers to work independently, ensuring interoperability between the methods and/or components that they are respectively developing.
-
An interface leaves room for alternative implementations to coexist, which share some functionalities. E.g.:
- alternative game modes in the example above,
- implementations that are specific to a specific hardware, operating system, …
- implementations that offer different guarantees on performances (like the multiple implementations of Java’s Collection interface and subinterfaces).
Inheritance #
An interface in Java can extend another interface, like a class can extend another class, with the same keyword extends
.
However, Java interfaces support multiple inheritance (whereas Java classes do not). This means that an interface $A$ can extend two interfaces $B$ and $C$ even if $B$ and $C$ do not extend each other.
Implementing an interface #
In Java, the keyword implements
is used to indicate that a class implements an interface.
For instance:
public class SnapshotImpl implements Snapshot {
...
}
Note. It is good practice to avoid a class and an interface with the same name (even if they describe the same objects). To differentiate them, you may for instance use the suffix
Impl
, as illustrated above.
Consider a class $C$ that implements an interface $I$ :
- if $C$ is abstract, then it can implement some of the methods declared in $I$,
- if $C$ is not abstract, then each method declared in $I$ must be implemented in $C$ (or some superclass of $C$), otherwise the program will not compile.
The program represented by the following diagram does not compile. Can you see why?
The class Butterfly
does not implement the method sleep
.
Note. In this example, the class
Butterfly
inherits an implementation ofboost
(fromAbstractUnit
), and overrides it. This is not an error.
Since Java interfaces support multiple inheritance, a Java class can implement several interface that do not extend each other.
For instance, the class Unicorn
could implement an interface Animal
and another interface ImaginaryCreature
, even though none of these two interfaces extends the other (not all animals are imaginary, and not all imaginary creatures are animals).
This would be declared as follows:
public class Unicorn extends MobileUnit implements Animal, ImaginaryCreature {
...
}
The program represented by the following diagram does not compile. Can you see why?
The class Unicorn
does not implement the method hide
.
Interfaces vs abstract classes #
In theory, Java’s interfaces and abstract classes serve different purposes:
- abstract classes are meant to factorize code (i.e. avoid redundant code), whereas
- interfaces are meant to document code and act as a contract.
However, in practice, they have partially overlapping features, which may be confusing.
In particular:
- an abstract class can have abstract methods, which behave similarly to interface methods (with the additional constraint that an abstract method must be implemented by at least one subclass).
- Since Java 8 (2014), interfaces can carry code, in so-called default methods. This feature was introduced for backward compatibility reasons, but arguably contradicts the meaning of the term “interface”.
Note. A benefit of default methods (i.e. methods implemented in interfaces) is that they allow multiple inheritance of implemented methods (since interfaces allow multiple inheritance), which is not possible with classes. For this reason, some authors (like Joshua Bloch) recommend using interfaces with default methods to play the role of abstract classes. However, this may be confusing for less experienced Java developers.
For this course, we chose to present Java interfaces from a more traditional (pre-Java 8) perspective, because this is arguably easier to understand, and still the dominant usage of Java interfaces. However, for your project, feel free to deviate from this model if you think that this is appropriate (in particular if you need multiple inheritance).