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.