Chapter 5 - Java for Beginners Course

Interfaces

There will be situations where you’ll need to define a contract between different parts of your system, or between your code and the code from a 3rd party (a different team/company/etc). These contracts don’t need to specify any implementation details, they only define the behaviors that are expected.

In general, interfaces are the definitions of these contracts defined in Java and are of great importance in the industry. They are an standard to define APIs (Application Programming Interfaces), enable the reduction of software coupling, amongst other benefits.

Interfaces are declared in a similar way to abstract classes, but instead of using the keywords abstract class we use the keyword interface:

[interface modifiers] interface InterfaceName [extends SuperInterfaceName] {

    // interface body (includes abstract methods, static methods, default methods and constant values)

}

For example:

public interface ExampleInterface extends OtherInterface {
	boolean doSomething(String input);
}

When using interfaces, it is important to note that:

  • Unlike classes, the default access modifier of all methods defined in an interface is public. You can specify it, but it isn’t required.

  • Interfaces don’t define/prescribe the state. They only define the behaviours required (methods).

  • Similar to abstract classes, we can’t instantiate an interface, for example, we can’t do new InterfaceName().

  • Interfaces can extend other interfaces (similar to inheritance in classes).

  • A class doesn’t extend an interface, however, a class can implement an interface (we’ll see an example below).

How to use interfaces?

For the examples below, we’ll use a variation of the LightHandler example we used before.

This is a very simple example that shows a basic usage of an interface and how you can use or come across them when developing in Java. Although in the example we use different companies, the same can occur across different teams in the same company, or different components in a single system developed by a single team, etc.

Let’s assume that we’re building a system for a Home Assistant that will allow users manage the lights of their house from a central place. Now, the lights could be designed by different companies and we should provide a way for them to integrate with our system.

For this scenario we decide to define a Java API that declares the following Light interface:

public interface Light {
    void turnOn();
    void turnOff();
}

This is what we’ll provide to companies that want to develop lights that integrate with our Home Assistant.

The LightHandler in our system is still the same:

public class LightHandler {
    public void turnOn(Light light) {
        light.turnOn();
    }

    public void turnOff(Light light) {
        light.turnOff();
    }
}

From the point of view of our LightHandler, we don’t know the implementation details of the Light object that will be provided as a parameter, all we know is that whatever object we’re provided as an input, it will implement the contract we defined in our Light interface, in our case, that it will have a turnOn() and a turnOff() method.

As shown above, we can use interfaces as any other type in Java. For example, we can define variables/parameters/fields that use the interface as the reference type, like in our turnOn method, the parameter is of type Light.

Implementing an interface

Now, let’s move to the other side of our problem. Let’s assume we’re a company that develops Wi-Fi connected lights (WiFiLightsCompany) and we want to integrate with the Home Assistant mentioned above.

All we know is that to integrate with the system we need to implement the Light interface.

Given that we can’t instantiate an interface and that classes can’t extend from them, we need to rely on a different way to be able to use them. In Java we must use the implements keyword to specify that a class will implement a given contract (interface).

public class WiFiLight implements Light {
    @Override
    public void turnOn() {
        System.out.println("Turning on the light via WiFi");
    }

    @Override
    public void turnOff() {
        System.out.println("Turning off the light via WiFi");
    }
}

Here we are declaring that our WiFiLight is a type of Light and as such it needs to implements the two methods defined in our Light interface.

Whereas a class can only extend one superclass, a class can implement multiple interfaces using a comma separated list of interfaces, like: public class WiFiLight implements Light, AnotherInterface.

For the purpose of the example, let’s assume there is another company that develops Bluetooth connected lights and that we have a second implementation of our Light interface.

public class BluetoothLight implements Light {
    @Override
    public void turnOn() {
        System.out.println("Turning on the light via Bluetooth");
    }

    @Override
    public void turnOff() {
        System.out.println("Turning off the light via Bluetooth");
    }
}

Let’s give the above a try:

In the example code run the JavaInterfacesApp application.
Light light1 = new WiFiLight();
Light light2 = new BluetoothLight();

LightHandler lightHandler = new LightHandler();
lightHandler.turnOn(light1);
lightHandler.turnOff(light2);

Output:

Turning on the light via WiFi
Turning off the light via Bluetooth

The idea with this last example is to show how our LightHandler implementation doesn’t need to know any details about how the lights are operated. All it needs to know is that it can turn them on/off as the lights follow an agreed contract.

The methods in our LightHandler can use Light as the parameter type and due to Polymorphism we can pass different types of objects, like light1 which is of type WiFiLight as a parameter value (WiFiLight is a type of Light).

Static methods in interfaces

Similar to classes, we can define methods that belong to the interface itself using the static keyword and in the same way you can invoke them using the name of the interface that owns them and the dot (.) operator.

Let’s assume we want to provide a utility method that allows users to test if a light is working or not. This method should basically turn the light on and off in sequence.

In this case, one way of approaching this is using a static method, like this:

static void testLight(Light lightToTest) {
    lightToTest.turnOn();
    lightToTest.turnOff();
}

and we can invoke the static method like this:

Light.testLight(light1);

Default methods in interfaces

The concept of default methods was introduced in Java 8 and allows interfaces to provide some default behavior or default implementation to a method in our contract that can be used by the implementing classes. An implementing class is free to override this default behavior if required.

Default methods are specified by the default keyword.

Following the previous example of testing a light, instead of using a static method, we could model it in a similar way using a default method. Default methods belong to the instances and as such we can invoke other non-static methods defined in the interface, as follows:

default void testLight() {
    System.out.println("Testing the light:");
    turnOn();
    turnOff();
}

Now, let’s give this a try:

Light light1 = new WiFiLight();
light1.testLight();

Output:

Testing the light:
Turning on the light via WiFi
Turning off the light via WiFi

In this example we’re creating a WiFiLight object, and from the code above we know that our WiFiLight doesn’t implement the testLight method. However, as we defined a default implementation in our Light interface our WiFiLight class will use that one and hence we can invoke it.