Java interfaces are different from classes, and it's important to know how to use their special properties in your Java programs. This tutorial introduces the difference between classes and interfaces, then guides you through examples demonstrating how to declare, implement, and extend Java interfaces.
You'll also learn how the interface has evolved in Java 8, with the addition of default and static methods, and in Java 9 with the new private methods. These additions make interfaces more useful to experienced developers. Unfortunately, they also blur the lines between classes and interfaces, making interface programming even more confusing to Java beginners.
What is a Java interface?
An interface is a point where two systems meet and interact. For example, you might use a vending machine interface to select an item, pay for it, and receive a food or drink item. From a programming perspective, an interface sits between software components. Consider that a method header (method name, parameter list, and so on) interface sits between external code that calls the method and the code within the method that will be executed as a result of the call. Here's an example:
System.out.println(average(10, 15));
double average(double x, double y) // interface between average(10, 15) call and return (x + y) / 2;
{
return (x + y) / 2;
}
What's often confusing to Java beginners is that classes also have interfaces. As I explained in Java 101: Classes and objects in Java, the interface is the part of the class that is accessible to code located outside of it. A class's interface consists of some combination of methods, fields, constructors, and other entities. Consider Listing 1.
Listing 1. The Account class and its interface
class Account
{
private String name;
private long amount;
Account(String name, long amount)
{
this.name = name;
setAmount(amount);
}
void deposit(long amount)
{
this.amount += amount;
}
String getName()
{
return name;
}
long getAmount()
{
return amount;
}
void setAmount(long amount)
{
this.amount = amount;
}
}
The Account(String name, long amount)
constructor and the void deposit(long amount)
, String getName()
, long getAmount()
, and void setAmount(long amount)
methods form the Account
class's interface: they are accessible to external code. The private String name;
and private long amount;
fields are inaccessible.
A method's code, which supports the method's interface, and that part of a class that supports the class's interface (such as private fields) is known as the method's or class's implementation. An implementation should be hidden from external code so that it can be changed to meet evolving requirements.
When implementations are exposed, interdependencies between software components can arise. For example, method code may rely on external variables and a class's users may become dependent on fields that should have been hidden. This coupling can lead to problems when implementations must evolve (perhaps exposed fields must be removed).
Java developers use the interface language feature to abstract class interfaces, thus decoupling classes from their users. By focusing on Java interfaces instead of classes, you can minimize the number of references to class names in your source code. This facilitates changing from one class to another (perhaps to improve performance) as your software matures. Here is an example:
List<String> names = new ArrayList<>()
void print(List<String> names)
{
// ...
}
This example declares and initializes a names
field that stores a list of string names. The example also declares a print()
method for printing out the contents of a list of strings, perhaps one string per line. For brevity, I've omitted the method's implementation.
List
is a Java interface that describes a sequential collection of objects. ArrayList
is a class that describes an array-based implementation of the List
Java interface. A new instance of the ArrayList
class is obtained and assigned to List
variable names
. (List
and ArrayList
are stored in the standard class library's java.util
package.)
When client code interacts with names
, it will invoke those methods that are declared by List
, and which are implemented by ArrayList
. The client code will not interact directly with ArrayList
. As a result, the client code will not break when a different implementation class, such as LinkedList
, is required:
List<String> names = new LinkedList<>()
// ...
void print(List<String> names)
{
// ...
}
Because the print()
method parameter type is List<String>
, this method's implementation doesn't have to change. However, if the type had been ArrayList<String>
, the type would have to be changed to LinkedList<String>
. If both classes were to declare their own unique methods, you might need to significantly change print()
's implementation.
Decoupling List
from ArrayList
and LinkedList
lets you write code that's immune to class-implementation changes. By using Java interfaces, you can avoid problems that could arise from relying on implementation classes. This decoupling is the main reason for using Java interfaces.
Declaring Java interfaces
You declare an interface by adhering to a class-like syntax that consists of a header followed by a body. At minimum, the header consists of keyword interface
followed by a name that identifies the interface. The body starts with an open-brace character and ends with a close brace. Between these delimiters are constant and method header declarations:
interface identifier
{
// interface body
}
By convention, the first letter of an interface's name is uppercased and subsequent letters are lowercased (for example, Drawable
). If a name consists of multiple words, the first letter of each word is uppercased (such as DrawableAndFillable
). This naming convention is known as CamelCasing.
Listing 2 declares an interface named Drawable
.
Listing 2. A Java interface example
interface Drawable
{
int RED = 1;
int GREEN = 2;
int BLUE = 3;
int BLACK = 4;
int WHITE = 5;
void draw(int color);
}
Drawable
declares five fields that identify color constants. This interface also declares the header for a draw()
method that must be called with one of these constants to specify the color used to draw an outline. (Using integer constants isn't a good idea because any integer value could be passed to draw()
. However, they suffice in a simple example.)
Drawable
identifies a reference type that specifies what to do (draw something) but not how to do it. Implementation details are consigned to classes that implement this interface. Instances of such classes are known as drawables because they know how to draw themselves.
Implementing Java interfaces
A class implements an interface by appending Java's implements
keyword followed by a comma-separated list of interface names to the class header, and by coding each interface method in the class. Listing 3 presents a class that implements Listing 2's Drawable
interface.
Listing 3. Circle implementing the Drawable interface
class Circle implements Drawable
{
private double x, y, radius;
Circle(double x, double y, double radius)
{
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw(int color)
{
System.out.println("Circle drawn at (" + x + ", " + y +
"), with radius " + radius + ", and color " + color);
}
double getRadius()
{
return radius;
}
double getX()
{
return x;
}
double getY()
{
return y;
}
}
Listing 3's Circle
class describes a circle as a center point and a radius. As well as providing a constructor and suitable getter methods, Circle
implements the Drawable
interface by appending implements Drawable
to the Circle
header, and by overriding (as indicated by the @Override
annotation) Drawable
's draw()
method header.
Listing 4 presents a second example: a Rectangle
class that also implements Drawable
.
Listing 4. Implementing the Drawable interface in a Rectangle context
class Rectangle implements Drawable
{
private double x1, y1, x2, y2;
Rectangle(double x1, double y1, double x2, double y2)
{
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
@Override
public void draw(int color)
{
System.out.println("Rectangle drawn with upper-left corner at (" + x1 +
", " + y1 + ") and lower-right corner at (" + x2 +
", " + y2 + "), and color " + color);
}
double getX1()
{
return x1;
}
double getX2()
{
return x2;
}
double getY1()
{
return y1;
}
double getY2()
{
return y2;
}
}
Listing 4's Rectangle
class describes a rectangle as a pair of points denoting the upper-left and lower-right corners of this shape. As with Circle
, Rectangle
provides a constructor and suitable getter methods, and also implements the Drawable
interface.
An interface type's data values are the objects whose classes implement the interface and whose behaviors are those specified by the interface's method headers. This fact implies that you can assign an object's reference to a variable of the interface type, provided that the object's class implements the interface. Listing 5 demonstrates.
Listing 5. Aliasing Circle and Rectangle objects as Drawables
class Draw
{
public static void main(String[] args)
{
Drawable[] drawables = new Drawable[] { new Circle(10, 20, 15),
new Circle(30, 20, 10),
new Rectangle(5, 8, 8, 9) };
for (int i = 0; i < drawables.length; i++)
drawables[i].draw(Drawable.RED);
}
}
Because Circle
and Rectangle
implement Drawable
, Circle
and Rectangle
objects have Drawable
type in addition to their class types. Therefore, it's legal to store each object's reference in an array of Drawable
s. A loop iterates over this array, invoking each Drawable
object's draw()
method to draw a circle or a rectangle.
Assuming that Listing 2 is stored in a Drawable.java
source file, which is in the same directory as the Circle.java
, Rectangle.java
, and Draw.java
source files (which respectively store Listing 3, Listing 4, and Listing 5), compile these source files via either of the following command lines:
javac Draw.java
javac *.java
Run the Draw
application as follows:
java Draw
You should observe the following output:
Circle drawn at (10.0, 20.0), with radius 15.0, and color 1
Circle drawn at (30.0, 20.0), with radius 10.0, and color 1
Rectangle drawn with upper-left corner at (5.0, 8.0) and lower-right corner at (8.0, 9.0), and color 1
Note that you could also generate the same output by specifying the following main()
method:
public static void main(String[] args)
{
Circle c = new Circle(10, 20, 15);
c.draw(Drawable.RED);
c = new Circle(30, 20, 10);
c.draw(Drawable.RED);
Rectangle r = new Rectangle(5, 8, 8, 9);
r.draw(Drawable.RED);
}
As you can see, it's tedious to repeatedly invoke each object's draw()
method. Furthermore, doing so adds extra bytecode to Draw
's class file. By thinking of Circle
and Rectangle
as Drawable
s, you can leverage an array and a simple loop to simplify the code. This is an additional benefit from designing code to prefer interfaces over classes.