Interfaces in Java

Learn the difference between classes and interfaces, then get started declaring, implementing, and extending interfaces in your Java programs

Interfaces in Java
baldiri (CC BY 2.0)

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.

download
Download the source code for example applications in this tutorial. Created by Jeff Friesen for JavaWorld.

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 Drawables. 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 Drawables, 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.

Caution!

1 2 3 Page 1
Page 1 of 3
InfoWorld Technology of the Year Awards 2023. Now open for entries!