January 17, 2024

lealceldeiro

What's a Java interface?

If you ask someone who's not a programmer, or software engineer, or the like, "what's an interface?", most probably you'll get answers around "it's a boundary where two entities interact", "it's some sort of connection between two things through which they interact with each other", or "it's the means through which two unrelated elements interact with each other".

Then, what's a Java interface?

Interfaces in Java define behaviors. These behaviors are expressed as a group of methods in the body of the interface.

Then, some class can implement the interface methods as it sees fit, that's it, it defines how the behaviors specified in the interface are actually implemented. This class would be the first "entity" from our previous wider definition of interface.

The second "entity", the one that would interact with the class implementation through the interface, would be the caller: the one that calls the methods.

In summary we could say: the interface defines what behaviors/methods it provides to callers, and that must be implemented by any concrete class. It establishes a well-defined API contract between two parties: one party that provides the implementation and another party that will consume it.

If we were to create a simulation of a restaurant to illustrate the usage of an interface. We could have three elements:

We could call these three elements like this, respectively:

How does an interface declaration look like in Java?

Let's look at an example, by following our previous analogy of the restaurant:


// interface declaration
interface OrderProcessor {
    // here we declare only the method (behavior) - but not the actual implementation
    public void bringFood(String dishName);
}

// a class that implements the interface
class Waiter implements OrderProcessor {
    // the actual logic of the method (body of the method) is defined here
  	public void bringFood(String dishName) {
	    System.out.println("Brought " + dishName + " to customer");
    }
}

// another class that can ask for food
class Customer {
    private String favoriteDish = "Fish and Vegetables";

    void askForFood(OrderProcessor orderProcessor) {
	    // the customer interacts with the interface OrderProcessor
        orderProcessor.bringFood(favoriteDish);
    }
}

This interface is called OrderProcessor and it defines the "bring food" behavior, which is declared as a method named: bringFood.

This is a very simple example; let's dive deeper into the intricacies of interfaces in Java.

Diving deeper into Java interfaces

Declaring an interface

There are two types of interface declarations:

In this article only the normal interface declaration is covered. Annotation interface declaration will be covered in a separate article.

Let's see the structure of an interface declaration:


{InterfaceModifier} interface TypeIdentifier [TypeParameters] [InterfaceExtends] [InterfacePermits] InterfaceBody

Where

It's important to note that unlike a class, an interface cannot be declared final. However, an interface may be declared sealed ( §9.1.1.4 ) to limit its subclasses and subinterfaces.

Interface body

The structure of an interface body ( {InterfaceMemberDeclaration} ) is as follows:


{
    ConstantDeclaration
	InterfaceMethodDeclaration
	ClassDeclaration
	InterfaceDeclaration
}

Let's see what each of these elements are, and some examples of how they're used.

Constants

Constants ( ConstantDeclaration ) can be referred to as interface attributes, properties, fields. They are similar to class fields, except they can NOT be modified —they're final—, and their declaration is as follows:


{ConstantModifier} UnannType VariableDeclaratorList;

In this case

Let's see an example of a constant in our OrderProcessor interface.


interface OrderProcessor {
    public int MAX_ORDERS_NUMBER = 1000;
    // ... rest of the code omitted for brevity
}

Here, we declared a constant MAX_ORDERS_NUMBER of type int with a value of 1000. It is public (default in interfaces), so it's publicly accessible by anyone with access to the interface.

Method declarations

Method declarations ( InterfaceMethodDeclaration ) are the declarations of the methods defined in the interface. They are the methods that must be implemented by any class that implements the interface, unless the interface itself provides a default implementation.

Default method implementations is a feature added in Java 8 that allows us to define the method body (its actual implementation) in the interface itself, as opposite to abstract methods, which require a concrete class to implement them.

More formally, a default method is an instance method declared in an interface with the default modifier. Its body is always represented by a block, which provides a default implementation for any class that implements the interface without overriding the method. These methods are distinct from concrete methods, which are declared in classes, and from private interface methods, which are neither inherited nor overridden ( §9.4 ).

Let's see now how to declare these methods in our OrderProcessor interface.


interface OrderProcessor {
    public int MAX_ORDERS_NUMBER = 1000;

    // abstract method -- must be implemented by the concrete class
    public void bringFood(String dishName);

    // also, an abstract method -- must be implemented by the concrete class
    public abstract void bringWater();

    // any class implementing the interface will inherit this
    // default method implementation, unless overridden
    default String getType() {
        return "Waiter";
    }

    // only available to be called within the interface itself
    private int getMaxOrders() {
        return MAX_ORDERS_NUMBER;
    }
}
Class declaration

Nested (or inner) class declarations ( ClassDeclaration ) were covered in a previous post ( Java classes and objects ). Let's quickly review an example of how it would look like in the body of an interface:


interface OrderProcessor {
    // nested class declaration
    class BillCalculator {
    }
    // ... rest of the code omitted for brevity
}
Interface Declaration

Nested interface declarations ( InterfaceDeclaration ) were also covered in that post . Let's see how to use it in our example.


interface OrderProcessor {
    interface Calculator {
    }
    // ... rest of the code omitted for brevity
}

Implementing an interface

An interface is not of much use without a concrete class that implements its methods. So, let's get into how a class can implement the methods defined in the body of an interface.

A class can be declared to directly implement one or more interfaces ( §9, §8.1.5 ). This means that any instance of that class implements all the abstract methods specified by the interface.

A class necessarily implements all the interfaces that its direct superclasses and direct superinterfaces do. This multiple interface inheritance allows objects to support multiple common behaviors without sharing a superclass (§9).

Let's review again our previous example.


interface OrderProcessor {
    public void bringFood(String dishName);
}

class Waiter implements OrderProcessor {
  	public void bringFood(String dishName) {
        System.out.println("Brought " + dishName + " to customer");
    }
}

Here, we specify after the keyword implements all the interfaces that the class implements. Notice that it's mandatory for the Waiter class to implement the method bringFood, otherwise the code won't compile.

Let's see how the Waiter class can implement another interface: Serializable, which belongs to the java.io package.


class Waiter implements OrderProcessor, Serializable {
  	public void bringFood(String dishName) {
	      System.out.println("Brought " + dishName + " to customer");
    }
}

As you notice, we only need to add the new interface being implemented after a comma (,). This way we can implement all the interfaces we want.

Extending an interface

Sometimes there are interfaces that do not provide all the method declarations we need. In such cases, sometimes we need to create an interface that "inherits" that other interface method declarations, and, on top of that, add our own. This way any class implementing the new interface will inherit all the methods. This is a common pattern used when implementing our own Spring Data JPA Repositories.

An interface may be declared to be a direct extension of one or more other interfaces, meaning that it inherits all the member classes and interfaces, instance methods, and static fields of the interfaces it extends, except for any members that it may override or hide ( §9).

Let's see in our example how would such extension look like.


interface Processor<T> {                              // 1
	public void process(T element);                   // 2
}

interface OrderProcessor extends Processor<String> {  // 3
    public void bringFood(String dishName);

	public default void process(String dishName) {    // 4
	    bringFood(dishName);
	}
}

There are several things going on in this example, so let's review it, point by point from the previous commented lines:

  1. Processor is declared as a generic interface that accepts a type T (this is the type of the elements the interface will work with). See §9.1.2 for more information about this.
  2. Processor declares an abstract method called process, which takes an argument of type T.
  3. OrderProcessor extends Processor and it specifies that the elements it will work with are of type String.
  4. OrderProcessor provides a default implementation for the process method, inherited from Processor. The default implementation is actually just a call to the abstract method bringFood and it will be inherited by any concrete class.

This example shows how powerful our implementations can be by combining different Java features.

Evolving interfaces

When an interface is defined and made available to client code (let it be our own code, library users, or third party vendors who rely on our interfaces definitions), it's extremely important to be careful with how we evolve it so we don't break existing code that use the current API contract defined by the interface.

While this is a topic for another whole article. There are some important points we should touch upon here. The following points are an extract from §13.5 .

Examples

Let's see some more examples.

A public generic interface with two constants, one private method and one public method


public interface Processor<T> {
    int MAX_PROCESSES = 5;
    boolean LANGUAGE_AGNOSTIC = true;

	public void process(T element);

    private boolean isValid(T e) {
        return e != null;
    }
}

A public interface with one static inner class and one static inner interface; both of them have package-private visibility


public interface OrderProcessor {
    class Sender {                  // <-- the 'static' keyword is default here before the keyword 'class'
    }
    static interface Calculator {
    }
}

A package-private interface with one public static nested interface


interface OrderProcessor {
    public static interface Calculator {
    }
}

A sealed interface that permits another sealed interface to extend it, and a final class that implements the intermediate interface


sealed interface Processor<T> permits OrderProcessor {
    void process(T element);
}
sealed interface OrderProcessor extends Processor<String> {
    void process(String order);
}
final class Waiter implements OrderProcessor {
  	public void process(String order) {
	    System.out.println("Brought " + order + " to customer");
    }
}

A sealed interface that permits another non-sealed interface to extend it, and a NON-final class that implements the intermediate interface


sealed interface Processor<T> permits OrderProcessor {
    void process(T element);
}
non-sealed interface OrderProcessor extends Processor<String> {
    void process(String order);
}
class Waiter implements OrderProcessor {
  	public void process(String order) {
	    System.out.println("Brought " + order + " to customer");
    }
}

A sealed interface that permits a final class to implement it


sealed interface OrderProcessor permits Waiter {
    void process(String order);
}
final class Waiter implements OrderProcessor {
  	public void process(String order) {
	    System.out.println("Brought " + order + " to customer");
    }
}

Why do we need interfaces?

While interfaces are not a mandatory component of the software we implement, it's true our programs can use interfaces to provide a common supertype for otherwise unrelated classes, and to make it unnecessary for related classes to share a common abstract superclass ( §9).

Many software patterns have been put into practice taking into account the concept of interfaces. These patterns solve well-known problems with a defined context and solution. Just to mention a few, we could point out SOLID, and many of the patterns presented by GoF; thus they bring flexibility, robustness, re-usability and a lot more benefits to our well-designed software solutions.

Nowadays, when systems are more complex, and we need to model more complex scenarios as well, interfaces play a vital role in promoting a loose coupling between the elements of our software. They're also a very powerful element of Java: they allow a class to inherit behaviors from multiple supertypes (so we can overcome the "limitation" of not having multiple inheritance in Java ), they play a pivotal role in polymorphism , they allow us to define clear boundaries and responsibilities for each software component and in most cases they make easier for us to implement automated test for the code we write.

Final thoughts

There is so much material we could revise about Java interfaces that this single article wouldn't be enough to cover it all. I've presented here high level information as well as several of the implementation details about this topic which I hope, along with the linked documentation, is a good start for you to review and deepen your knowledge about Java.