Interfaces in Java

how-to
15 Jul 201928 mins
Core JavaJavaProgramming Languages

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

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!

When you implement an interface method (by overriding the interface’s method header), remember that all of the methods whose headers are declared in the interface are implicitly declared public. If you forget to include public in the implemented method’s declaration, the compiler will report an error informing you that you’re attempting to assign weaker access to the implemented method.

Implementing multiple interfaces

Earlier, I mentioned that a class can implement multiple interfaces. Each interface’s name is specified as part of a comma-separated list of names that follows the implements keyword. Listing 6 presents a simple example where class C implements interfaces A and B.

Listing 6. Implementing multiple interfaces

interface A
{
   // appropriate constants and/or method headers
}
interface B
{
   // appropriate constants and/or method headers
}
class C implements A, B
{
   // override A's and B's method headers
}

Beware of the potential for name collisions when implementing multiple interfaces. This occurs when the same constant name appears in each interface, possibly with different type and/or other information, and is accessed in the class. When a name collision occurs, the compiler will report an error, which is demonstrated in Listing 7.

Listing 7. Demonstrating colliding constants

interface A
{
   int CONSTANT = 2;
   void method();
}
interface B
{
   int CONSTANT = 3;
   int method(int x);
}
class C implements A, B
{
   int x = CONSTANT;
   @Override
   public void method()
   {
   }
   @Override
   public int method(int x)
   {
      return x;
   }
}

Here, class C is inheriting two different constants named CONSTANT that are initialized to two different values. The Java compiler cannot determine which constant should be inherited by C (the same problem would occur if each constant was assigned the same value) and reports the following error message:

C.java:15: error: reference to CONSTANT is ambiguous
   int x = CONSTANT;
           ^
  both variable CONSTANT in A and variable CONSTANT in B match
1 error

Extending Java interfaces

A class that implements an interface reveals interface inheritance. The class inherits the interface’s constants and method headers, which it overrides. For example, each of Circle and Rectangle inherits Drawable‘s five integer constants and draw() method header.

Interface inheritance is also demonstrated when an interface extends another interface. Just as a subclass can extend a superclass via reserved word extends, you can use this reserved word to have a subinterface extend a superinterface. Listing 8 demonstrates.

Listing 8. Declaring a subinterface that extends a superinterface


interface Fillable extends Drawable
{
   void fill(int color);
}

Fillable extends Drawable, inheriting its color constants and draw() method header. Fillable also declares the header for a fill() method that must be called with one of these constants to specify the color used to fill an interior. (Fillable extends Drawable to support drawing an outline as well as filling an interior.)

You could retrofit the previous Circle and Rectangle classes to support Fillable by performing the following steps:

  1. Change implements Drawable to implements Fillable. There is no need to specify either implements Drawable, Fillable or implements Fillable, Drawable because Fillable includes all of Drawable by extension.
  2. Override the fill() method header in the same manner as overriding the draw() method header.

Listing 9 presents an equivalent Fill application that demonstrates the Fill interface.

Listing 9. Aliasing Circle and Rectangle objects as Fillables

class Fill
{
   public static void main(String[] args)
   {
      Fillable[] fillables = new Fillable[] { new Circle(10, 20, 15), 
                                              new Circle(30, 20, 10),
                                              new Rectangle(5, 8, 8, 9) };
      for (int i = 0; i < fillables.length; i++)
      {
         fillables[i].draw(Drawable.RED);
         fillables[i].fill(Fillable.BLACK);
      }
   }
}

Circle and Rectangle implement Fillable, giving Circle and Rectangle objects a Fillable type in addition to their class types. Therefore, it’s legal to store each object’s reference in an array of Fillables. A loop iterates over this array, invoking each Fillable‘s inherited draw() and non-inherited fill() methods to draw and fill a circle or a rectangle.

If Listing 2 is stored in Drawable.java, which is in the same directory as Circle.java, Rectangle.java, Fillable.java, and Fill.java (respectively storing Listing 3 and Listing 4, with updates, Listing 8, and a source file that stores Listing 9) you can compile these source files using either of the following command lines:

javac Fill.java
javac *.java

Run the Fill application as follows:

java Fill

You should observe the following output:

Circle drawn at (10.0, 20.0), with radius 15.0, and color 1
Circle filled at (10.0, 20.0), with radius 15.0, and color 4
Circle drawn at (30.0, 20.0), with radius 10.0, and color 1
Circle filled at (30.0, 20.0), with radius 10.0, and color 4
Rectangle drawn with upper-left corner at (5.0, 8.0) and lower-right corner at (8.0, 9.0), and color 1
Rectangle filled with upper-left corner at (5.0, 8.0) and lower-right corner at (8.0, 9.0), and color 4

You can upcast the interface type of an object from a subinterface to a superinterface because a subinterface is a kind of superinterface. For example, you could assign a Fillable reference to a Drawable variable and then invoke Drawable‘s draw() method on the variable:

Drawable d = fillables[0];
d.draw(Drawable.GREEN);

Extending multiple interfaces

As with interface implementation, you can extend multiple interfaces. Each interface’s name is specified as part of a comma-separated list of names that follows the extends keyword. Listing 10 presents a simple example where interface C extends interfaces A and B.

Listing 10. Extending multiple interfaces

interface A
{
   // appropriate constants and/or method headers
}
interface B
{
   // appropriate constants and/or method headers
}
interface C extends A, B
{
   // appropriate constants and/or method headers
}

Beware of the potential for name collisions when extending multiple interfaces. This occurs when the same constant name appears in each superinterface, possibly with different type and/or other information, and is accessed in the subinterface. When a name collision occurs, the compiler will report an error, which is demonstrated in Listing 11.

Listing 11. Demonstrating colliding constants

interface A
{
   int CONSTANT = 2;
   void method();
}
interface B
{
   int CONSTANT = 3;
   int method(int x);
}
interface C extends A, B
{
   int CONSTANT2 = CONSTANT;
}

Here, interface C is inheriting two different constants named CONSTANT that are initialized to two different values. The Java compiler cannot determine which constant should be inherited by C (the same problem would occur if each constant was assigned the same value) and reports the following error message:

C.java:15: error: reference to CONSTANT is ambiguous
   int CONSTANT2 = CONSTANT;
                   ^
  both variable CONSTANT in A and variable CONSTANT in B match
1 error

Evolving the interface in Java 8: Default and static methods

Java SE 8 introduced two significant enhancements to interfaces: default and static methods. These enhancements have their uses, but make interfaces more like classes–to the point where you can base some applications on interfaces instead of classes.

Default methods

A default method is a concrete instance method that’s defined in an interface and whose method header is prefixed with the default keyword. An example of a default method in Java’s standard class library is java.util.Comparator<T>‘s default Comparator<T> reversed() method.

To understand the usefulness of interface-based default methods, suppose you’ve created Listing 12’s Drivable interface, which describes any kind of drivable in terms of its basic operations. (I could have called this interface Vehicle but would a horse qualify as a vehicle?)

Listing 12. Declaring a Drivable interface

public interface Drivable
{
   public void drive(int numUnits);
   public void start();
   public void stop();
   public void turnLeft();
   public void turnRight();
}

Being satisfied with Drivable, you make this interface available to other developers who use it extensively in their applications. For example, Listing 13 shows a simple application that introduces a Car class, which implements Drivable, along with a DMDemo demonstration class.

Listing 13. Implementing Drivable in a Car class

class Car implements Drivable
{
   @Override
   public void drive(int numUnits)
   {
      System.out.printf("Driving car %d kilometers%n", numUnits);
   }
   @Override
   public void start()
   {
      System.out.println("Starting car");
   }
   @Override
   public void stop()
   {
      System.out.println("Stopping car");
   }
   @Override
   public void turnLeft()
   {
      System.out.println("Turning car left");
   }
   @Override
   public void turnRight()
   {
      System.out.println("Turning car right");
   }
}
public class DMDemo
{
   public static void main(String[] args)
   {
      Car car = new Car();
      car.start();
      car.drive(20);
      car.turnLeft();
      car.drive(10);
      car.turnRight();
      car.drive(8);
      car.stop();
   }
}

Note that @Override is an annotation (metadata) telling the compiler to ensure that the superclass actually declares a method that the annotated method overrides, and report an error when this isn’t the case. I’ll introduce annotations later in this series.

Compile Listing 13 and Listing 12 (javac DMDemo.java) and run the application (java DMDemo). You’ll observe the following output:

Starting car
Driving car 20 kilometers
Turning car left
Driving car 10 kilometers
Turning car right
Driving car 8 kilometers
Stopping car

Suppose your manager now wants you to add a demo() method to Drivable. Adding a method to an interface breaks binary compatibility, so every class implementing Drivable must be retrofitted to also implement demo(). To avoid this aggravation, you add demo() to Drivable as a default method, as shown in Listing 14.

Listing 14. Declaring a Drivable interface with a default method

public interface Drivable
{
   public void drive(int numUnits);
   public void start();
   public void stop();
   public void turnLeft();
   public void turnRight();
   public default void demo()
   {
      start();
      drive(20);
      turnLeft();
      drive(10);
      turnRight();
      drive(8);
      stop();
   }
}

demo() exists as an instance method with a default implementation. Because it doesn’t need to be implemented by classes that implement Drivable, older code won’t break when executed with a binary version of this interface. Listing 15 proves that binary compatibility isn’t compromised and shows how to invoke demo().

Listing 15. Invoking Drivable’s default method via a Car object reference

class Car implements Drivable
{
   @Override
   public void drive(int numUnits)
   {
      System.out.printf("Driving car %d kilometers%n", numUnits);
   }
   @Override
   public void start()
   {
      System.out.println("Starting car");
   }
   @Override
   public void stop()
   {
      System.out.println("Stopping car");
   }
   @Override
   public void turnLeft()
   {
      System.out.println("Turning car left");
   }
   @Override
   public void turnRight()
   {
      System.out.println("Turning car right");
   }
}
public class DMDemo
{
   public static void main(String[] args)
   {
      Car car = new Car();
      car.demo();
   }
}

Listing 15 shows that the Car class hasn’t been modified; it doesn’t have to implement demo(). It also shows that demo() is invoked like any other instance method that’s a member of the Car class. If you run this application, you’ll observe the same output as shown previously.

Java 8 introduced default methods to evolve the Java Collections Framework, so that it could support the new Streams API without breaking backward compatibility. New default Stream<E> parallelStream() and default Stream<E> stream() methods were added to the java.util.Collection<E> interface to bridge between Collections and Streams. Default methods can also be used to better organize library methods. For example, now that default void sort(Comparator<? super E> c) has been added to java.util.List<E>, you can more naturally write myList.sort(null); instead of Collections.sort(myList);.

Mastering default methods

If you want to master default methods, there are a few more tricks you should know. First, you can override a default method when necessary. Because default methods are implicitly public, don’t assign a weaker access privilege when you override one. If you do that you’ll receive a compiler error. Listing 16 shows how to override demo().

Listing 16. Excerpting Car from a third version of the DMDemo application

class Car implements Drivable
{
   @Override
   public void demo()
   {
      start();
      drive(20);
      stop();
   }
   @Override
   public void drive(int numUnits)
   {
      System.out.printf("Driving car %d kilometers%n", numUnits);
   }
   @Override
   public void start()
   {
      System.out.println("Starting car");
   }
   @Override
   public void stop()
   {
      System.out.println("Stopping car");
   }
   @Override
   public void turnLeft()
   {
      System.out.println("Turning car left");
   }
   @Override
   public void turnRight()
   {
      System.out.println("Turning car right");
   }
}

Second, you can invoke the default implementation of a method that has been overridden or isn’t available because of conflicting default implementations in different interfaces. You do this by prefixing keyword super with the name of the interface containing the default method, as shown below.

Listing 17. Accessing default methods with the keyword super

interface A 
{
   default void method()
   {
      System.out.println("A.method() invoked");
   }
}
public class DMDemo implements A
{
   @Override
   public void method()
   {
      System.out.println("DMDemo.method() invoked");
      A.super.method();
   }
   public static void main(String[] args)
   {
      DMDemo dmdemo = new DMDemo();
      dmdemo.method();
   }
}

After compiling Listing 17 (javac DMDemo.java), run the application (java DMDemo) and you’ll observe the following output:

DMDemo.method() invoked
A.method() invoked

Finally, you cannot implement any of Object‘s public non-final methods as default methods in an interface. Brian Goetz has explained the rationale for this restriction in a posting to the Project Lambda mailing list.

Static methods

A static method is a method that’s associated with the class in which it’s defined. Every object shares the static methods of its class. Java 8 also lets you define static methods in interfaces. An example from Java’s standard class library is java.lang.invoke.MethodHandleInfo‘s static String referenceKindToString(int referenceKind) method.

To understand the usefulness of interface-based static methods, consider a Drawable interface with a draw() method whose int argument contains the drawing color. It’s convenient to express this color as red, green, and blue components, so you add an rgb() static method that converts these components to an int. Check out Listing 18.

Listing 18. Declaring a Drawable interface with a static method

public interface Drawable
{
   public void draw(int color);
   public static int rgb(int r, int g, int b)
   {
      return r << 16 | g << 8 | b;
   }
}

Continuing, you create an application consisting of a Circle class that describes a circle via integer-based center coordinates and a radius, and which implements Drawable to draw a circle. You also create an SMDemo class whose main() method runs the application. Listing 19 presents the source code for these classes.

Listing 19. Accessing Drawable’s static method

class Circle implements Drawable
{
   private int x, y, r;
   Circle(int x, int y, int r)
   {
      this.x = x;
      this.y = y;
      this.r = r;
   }
   @Override
   public void draw(int color)
   {
      System.out.printf("Drawing circle(%d, %d, %d) in color %x%n", x, y, r, 
                        color);
   }
}
public class SMDemo
{
   public static void main(String[] args)
   {
      Circle circle = new Circle(10, 20, 5);
      circle.draw(Drawable.rgb(0x80, 0x60, 0x40));
   }
}

SMDemo‘s main() method instantiates Circle and then calls the object’s draw() method to draw the circle. Notice that the rgb() method call is prefixed by this static method’s interface. You cannot prefix this method with the interface-implementing class (as in Circle.rgb(0x80, 0x60, 0x40)) because the static method belongs to the interface.

Compile Listing 19 and Listing 18 (javac SMDemo.java) and run the application (java SMDemo). You’ll observe the following output:

Drawing circle(10, 20, 5) in color 806040

Java 8 introduced interface-based static methods because it’s often convenient to associate utility methods with interfaces instead of associating them with utility classes. Doing this makes source code more readable, and ensures that binary compatibility isn’t broken.

A word about interface-based applications

Before Java 8, a Java application consisted of at least one class that declared a main() entry-point method. Starting with Java 8, you can accomplish this task with an interface. For example, consider the SayHello application whose source code appears in Listing 20.

Listing 20. Declaring an interface-based SayHello application

public interface SayHello
{
   public static void main(String[] args)
   {
      if (args.length == 0)
         System.out.println("Hello!");
      else
         System.out.printf("Hello %s!%n", args[0]);
   }
}

The code in Listing 20 could be written just the same with an interface or a class. Starting with Java 8, you could compile this code (javac SayHello.java) without error. You would then run the application and observe appropriate output based on whether or not you specified any command-line arguments. However, an interface-based application type isn’t fully equivalent to a class-based application type. For example, unlike with a class, you cannot instantiate an interface. As a result, you cannot declare non-static fields in an interface. Regarding fields, you can declare only static constants in an interface: interfaces don’t support static fields with mutable state.

For now, it is probably best to avoid creating interface-based applications, because they don’t compile with a pre-Java 8 compiler and have the potential to confuse Java beginners. Although this feature works with Oracle’s Java 8 reference implementation, it’s possible that a future implementation might prevent interface-based applications from running.

Evolving the interface in Java 9: Private methods

Java SE 9 introduced one significant enhancement to interfaces: private methods. A private method is a concrete instance or static method that’s defined in an interface and whose method header is prefixed with the private keyword. Private methods were introduced to let an interface’s non-abstract methods share code (reducing code duplication).

An abstract example

I’ve created a short PMDemo application that demonstrates private methods in an abstract manner. See Listing 21 for the application’s source code.

Listing 21. Demonstrating an interface’s private instance and static methods

interface I
{
   default void a()
   {
      System.out.println("a() called");
      c();
   }

   default void b()
   {
      System.out.println("b() called");
      c();
   }

   private void c()
   {
      System.out.println("c() called");
   }

   static void d()
   {
      System.out.println("d() called");
      e();
   }

   private static void e()
   {
      System.out.println("e() called");
   }
}

public class PMDemo implements I
{
   public static void main(String[] args)
   {
      PMDemo pmdemo = new PMDemo();
      pmdemo.a();
      System.out.println();
      pmdemo.b();
      System.out.println();

      // The following method call will result in a compiler error because c()
      // is private to interface I.

      // pmdemo.c();

      I.d();
   }

   @Override
   public void b()
   {
      System.out.println("overriding b() called");
      I.super.b();
   }
}

Listing 21 declares a private instance method (c()) and a private static method (e()). The former method is executed by default methods a() and b(), and the latter method is executed by static method d().

Compile Listing 21 (javac PMDemo.java) and run the application (java PMDemo). You’ll observe the following output:

a() called
c() called

overriding b() called
b() called
c() called

d() called
e() called

A practical example

The former application doesn’t show the usefulness of private methods. To address this deficiency, I’ve created a second application consisting of Sortable, Employee, and SortableDemo classes. Listing 22 presents Sortable.

Listing 22. Describing a sorting capability for objects

public interface Sortable
{
   public int compareTo(Object o);

   static void sort(Sortable[] x, int a, int b)
   {
      int i, j;
      Sortable t;

      // a is the low index of the partition being sorted, whereas b is the high
      // index. When a >= b, either the partition is sorted or this method was
      // called with illegal values for a and b. In either case, no further work 
      // is done on this partition.

      if (a < b)
      {
         // x[a] is the pivot. Set i to pivot's initial position. Set j to the
         // end of the array (plus one because the inner loop decrements j prior
         // to making a comparison).

         i = a;
         j = b + 1;

         // Determine the pivot's final position. Make sure all integers less
         // than the pivot are on the left of that position and all integers
         // greater than the pivot are on the right of that position.

         do
         {
            // i < b serves as a sentinel in the following loop. This sentinel
            // prevents an ArrayIndexOutOfBoundsException when x[a] is larger 
            // than all subsequent data items. x[a] is the sentinel in the 
            // second loop.

            do { i++; } while (x[i].compareTo(x[a]) < 0 && i < b);
            do { j--; } while (x[j].compareTo(x[a]) > 0);

            if (i >= j)
               break;

            swap(x, i, j);
         }
         while (true);

         // Place pivot in appropriate position.

         swap(x, a, j);

         // Sort partition of Sortables less than pivot position.

         sort(x, a, j - 1);

         // Sort partition of Sortables greater than pivot position.

         sort(x, j + 1, b);
      }
   }

   private static void swap(Sortable[] s, int x, int y)
   {
      Sortable temp = s[x];
      s[x] = s[y];
      s[y] = temp;
   }
}

The Sortable interface introduces a sorting capability for objects. The class of those objects that are to be sorted implements Sortable and overrides its abstract compareTo() method. An application calls Sortable.sort() with an array of Sortables to be sorted along with the start and end indexes of the array as arguments. This method performs the sort via the Quicksort algorithm. It depends on a private static swap() method to swap array elements.

Listing 23 presents the source code to an Employee class that implements Sortable.

Listing 23. Describing a sortable employee

public class Employee implements Sortable
{
   private String name;

   public Employee(String name)
   {
      this.name = name;
   }

   @Override
   public int compareTo(Object o)
   {
      Employee e = (Employee) o;
      return name.compareTo(e.getName());
   }

   public String getName()
   {
      return name;
   }
}

Listing 23 overrides compareTo() to compare the current Employee with an Employee argument based on the employee’s name. (For brevity, I haven’t included any error checking in case a non-Employee object is passed to compareTo().) The compareTo() method relies on the String class’s compareTo() method to perform the actual comparison.

Listing 24 presents a SortableDemo class that combines these source files into an application.

Listing 24. Sorting an array of employees

public class SortableDemo
{
   public static void main(String[] args)
   {
      Employee[] employees = 
      { 
         new Employee("Paul"),
         new Employee("John"),
         new Employee("Zenia"),
         new Employee("Rashid"),
         new Employee("Felix")
      };
      Sortable.sort(employees, 0, employees.length - 1);
      for (int i = 0; i < employees.length; i++)
         System.out.println(employees[i].getName());
   }
}

Compile Listings 24, 23, and 22 (javac SortableDemo.java) and run the application (java SortableDemo). You should observe the following output:

Felix
John
Paul
Rashid
Zenia

Conclusion

The interface is a useful language feature that often confuses newcomers to the Java language. In this Java 101 tutorial, I’ve explained how interfaces differ from classes, and showed you how to declare, implement, and extend interfaces in your Java programs. I also introduced the two new features added to interfaces in Java SE 8, default and static methods, and the single new feature added to interfaces in Java SE 9, private methods.

Exit mobile version