This tutorial will introduce you to new features of interfaces along with functional interfaces introduced in Java 8.
Abstract
Java 8 is all about making code more concise and elegant. This increases the code readability and maintainability. In this tutorial, we will discuss the changes introduced in interfaces in Java 8 that will help us lighten our code base.
We will first be starting with how we used to work with interfaces and abstract classes till Java 7. After that, we will see how Java 8 helps us achieve same functionality with less code and more readability/maintainability.
Interfaces in Java are reference types and contains method definitions (abstract methods). It also acts as a contract for its implementations as all sub classes need to implement all of its methods. Additionally, these are commonly used to support multiple type implementations by having it implemented by multiple classes.
E.g. We need to design a EmployeeService that manages the employees of a company. However, We need to support following operations in our employee service -
- Persist the employee information in data store
- Fetch the emplyee using the emplyee Id
To handle this, we will define a reference type (contract) by creating an IEmployeeService interface defining the methods for above listed functionalities. This ensures that all employee services will need to provide implementations for these methods. This is how our simple IEmployeeService interface looks like -
/**
* Defines methods for employee services.
*/
public interface IEmployeeService {
/**
* Persists the input employee object information to employee store.
*/
void persist(Employee employee);
/**
* Retrieves and returns data for the employee with given id.
*/
Employee get(String employeeId);
}
Second step will be to create a class EmployeeService for implementing these methods. Below is stub of this service class -
/**
* Stub of an employee service.
*/
public class EmployeeService implements IEmployeeService{
@Override
public void persist(Employee employee) {
// Logic for persisting the input employee in data store
}
@Override
public Employee get(String id) {
// Logic for getting employee from data store
return new Employee();
}
}
This all works fine but things become interesting when we are asked to put different behaviours for different types of employees. For example, if we are given following additional requirement to be implemented in Employee Service -
Before persisting employee data into database, prefix Permanent employee id with "PERM" and contractual employee id with "CONT".
Firstly, some of us may get tempted to put one if condition on employee type in persist method and put above logic in same method. While this will work fine, it is not considered a good solution as -
- It breaks Open-Close principle as you are changing the existing code that may break existing working code. We should always ensure that a piece of code should not be changed and rather use inheritance or abstraction to introduce additional logic.
- It also breaks Single Responsiblility pattern as you are handing both Permanent and Contractual employee logic in same class.
- Finally, we may see this change as just one of several changes to come along on same lines. i.e. there may be other rules specific to permanent and contractual employees in future.
Above points give us the hint of segregating the handling of Permanent and Contractual employees to different classes. So, we decide to replace EmployeeService with following two different services -
PermanentEmployeeService
/**
* Provides implementation of employee service for Permanent Employees
*/
public class PermanentEmployeeService implements IEmployeeService{
@Override
public void persist(Employee employee) {
// Prefix employee id with "PERM"
// Persist employee in store
}
@Override
public Employee get(String employeeId) {
// Logic for getting employee from data store
return new Employee();
}
}
ContractualEmployeeService
/**
* Provides implementation of employee service for Contractual Employees
*/
public class ContractualEmployeeService implements IEmployeeService {
@Override
public void persist(Employee employee) {
// Prefix employee id with "CONT"
// Persist employee in store
}
@Override
public Employee get(String employeeId) {
// Logic for getting employee from data store
return new Employee();
}
}
It looks better now as any Permanent or Contratual employee specific rule will not impact other. However, we still have a challenge that our get() method is same for both employees and will result into duplicate code in our classes. How do we solve this problem? Till Java 7, we will think of introducing an Abstract class and move the implementation of get() method there.
This however requires us to introduce a unnecessary additional class making our code base bigger without providing much value. What if there was some mechanism to have methods implementations in interfaces itself? Sounds Interesting? This is where default methods introduced in Java 8 come handy!
Default Methods in Interfaces
Starting Java 8, we can provide implementations in interfaces. In our use case, since get() method is same for both implemenations, we will move it to our interface IEmployeeService as shown below (notice default keyword in method signature) -
/**
* Defines methods for employee services.
*/
public interface IEmployeeService {
/**
* Persists the input employee object information to employee store.
*/
void persist(Employee employee);
/**
* Retrieves and returns data for the employee with given id.
*/
default Employee get(String employeeId) {
// Logic for getting employee from data store
return new Employee();
}
}
This functionality is quite useful and can always be used to provide default implementations of methods. This will help us get rid of Abstract classes in many scenarios such as the one that we discussed in this tutorial.
However, it is important to note that default methods are NOT absolute replacement to Abstract classes. This is clearly evident from fact that you can only have public methods in public interfaces while Abstract classes can have private, default, protected and public methods. Abstract classes are quite useful while implementing Template design pattern that is based on introducing protected abstract methods using abstract classes.
Static Methods in Interfaces
Just like default methods, Java 8 interfaces can also have static utility methods. It is also quite useful feature as we can put interface specific utility methods to be utilized by interface implementation classes.
As a very trivial example, we can put a static method in our IEmployeeService interface to append tokens to employee id as follows. This promotes code re-use and binds utilities to reference types (contracts) -
/**
* Defines methods for employee services.
*/
public interface IEmployeeService {
/**
* Persists the input employee object information to employee store.
*/
void persist(Employee employee);
/**
* Retrieves and returns data for the employee with given id.
*/
default Employee get(String employeeId) {
// Logic for getting employee from data store
return new Employee();
}
/**
* Prefixes the input token to input employee id and returns the modified employee id.
*/
static String prefixEmployeeId(String employeeId, String token) {
if(employeeId == null || token == null) {
return employeeId;
}
return employeeId + token;
}
}
Functional Interfaces
Functional interfaces are a special subset of interfaces in Java 8. Here are the characteristics of an interface that make it Functional interface
- Interface can have exactly one abstract method (non-default and non-static method)
- Interface can have any number of default methods (no restrictions on default methods)
- Interface can have any number of static methods (no restrictions on static methods)
As we can note that only restriction for Functional interface is that it must have one and only one abstract method. Functional interfaces are basis of Lambda expressions introduced in Java 8. We will be covering Lambda expressions in subsequent tutorials (you can subscribe to our tutorials here).
Surprisingly, our IEmployeeService is also a functional interface as it only has one abstract method - persist(Employee).
@FunctionalInterface Annotation
Since functional interfaces are basis of Lambda expressions, it is very important to protect integrity of these interfaces i.e. how do we ensure that we don't add/remove abstract methods from a functional interface.
This is where @FunctionalInterface annotation come handy. You can use this annotation on top of any functional interface as below and java compiler will give you compilation error if you try to add/remove abstract methods from it.
/**
* Defines methods for employee services.
*/
@FunctionalInterface
public interface IEmployeeService {
/**
* Persists the input employee object information to employee store.
*/
void persist(Employee employee);
/**
* Retrieves and returns data for the employee with given id.
*/
default Employee get(String employeeId) {
// Logic for getting employee from data store
return new Employee();
}
/**
* Prefixes the input token to input employee id and returns the modified employee id.
*/
static String prefixEmployeeId(String employeeId, String token) {
if(employeeId == null || token == null) {
return employeeId;
}
return employeeId + token;
}
}
Note: This annotation does not convert an interface into Functional interface and instead just ensures its integrity. It works same way as @Override annotation that makes sure that you are actually overriding a method.
Thank you for reading through the tutorial. In case of any feedback/questions/concerns, you can communicate same to us through your comments and we shall get back to you as soon as possible.