Interfaces in Java are much like any other language. They are a contract which states the behaviours (methods) an implementing Class must have.
Declaration
interface MyContract{ // interface fields MUST be public static final public static final String someConstant = "constant field" String someOtherConstant = "fields are implcitly public static final" // interface methods can only be public and have no body public void someBehaviour1(); String someBehaviour2(String a); // public by default // static methods must have a body and can only be private or public static void staticBehaviour1() { // public by default System.out.println("helper method"); } private static void staticBehaviour1() { // for implementing class only System.out.println("helper method"); } // but, they can have a body if they are default default String defaultBehaviour1(int a) { return String.valueOf(a); }}
Fields must be public static final
Why public?:
Interfaces are contracts. All parts of a contract must be accessible/visible to be of any use!
Why static?:
Interfaces can’t be instantiated, so its fields have to be static.
Generally, methods must be public and have no body (they are public by default)
Well, the point of interfaces is to describe a set of behaviours that a class must exhibit. So it wouldn’t make sense for there to be methods that are private or protected…
The same reasoning applies to why the methods have no body, it is not the job of the interface to implement the behaviour, it merely enforces the behaviour’s existence.
static methods! (MUST HAVE BODY)
These are used to provide helper methods that are related to the Interface.
They can only be public (default) or private, and must have a concrete implementation.
private methods (MUST HAVE BODY)
These are for static or default methods, so they can be split and organised into smaller methods for reuseability/readability.
What about default methods? (MUST HAVE BODY)
Allowing implementation to be defined in the interface with the default keyword seems to be antithetical to the purpose of Interfaces that we just discussed.
SO why can Interfaces have default methods?
It’s so Interfaces can be updated to require new methods without having to update ALL the downstream Classes that use the Interface to include those new methods.
Let’s say we are developing a product and start with a pretty simple Interface to be used by Classes that can represent themselves as an image bitmap:
interface Drawable { Image draw();}
Over time, our requirements changed, and in newer code, we also require Classes that are Drawable to also be able to give us a name so that the save image file dialog can give the file a sensible default name.
But now you have to go back into your old code and modify every Class in your codebase that uses this Interface.
Implementing the getName() method despite it not being used by the old code at all.
With default, you can add new methods to the Interface without breaking older code!
Example of use in Java's Standard Library
In the OpenJDK source for the Iterable Interface, you can see the prior to Java 8, the Interface only had one method. But, in Java 8, they added 2 new methods with a default implementation so as to allow people to upgrade from previous Java versions to 8 without having to implement those methods in their code base.
Inheriting an Interface
Java Interface Inheritance
Interface Inheritance creates an “has-a” relationship
Each Class can implement any number of Interfaces
Fields can only be, and are public static final by default
Methods are public by default and can explicitly be private (>= Java 9)
Methods are only signatures and have no implementation body unless they are static or default
Methods can be static or default which require concrete implementation. see here
Purpose:
Interfaces in Java actually have 2 main purposes !!
Behavioural/Contractual Interfaces
Interfaces are often described as contracts. While classes describe a whole ‘thing’, Interfaces only describe behaviour.
A Cat should be described by a class, it has properties like hungerLevel and behaviour like eat() that relate to each other. But we could implement an Interface called Trained which describes behaviours like Sit() or RollOver(). Now our Cat is able to sit and roll-over! But of-course, these behaviours are not exclusive to Cat, they can be implemented by Dog, Cow… whatever! That’s the point of interfaces. To assign specific shared behaviours you want your classes to have.
A key example of a Behavioural Interface from the Java standard library is Comparable<T>.
This Interface defines the behaviour/method int compareTo(T o) which the implementing Class then has to Override, giving it the behaviour of being comparable! int compareTo(T o) is used by an object to compare itself to another object. Giving objects of this Class a natural order.
The Collections Class in Java, defines a method named sort which has the following signature:
public static <T extends Comparable<? super T>> void sort(List<T> list)
This is a generic method using generic wildcards to express that its parameter List<T> list can only take in a List which has elements that have a natural order, which is required for the sorting algorithm.
It does this by enforcing the generic T of the list must be of a type/Class that implements Comparable<T>.
So, by implementing the Comparable<T> Interface, we will have given the Class and its objects a natural order which will allow us to sort it by calling Collections.sort(List<T> list).
Sample Comparable<T> Code
import java.util.Arrays;import java.util.Collections;import java.util.List;public class Main { public static void main(String[] args) { List<Student> list = Arrays.asList( new Student(5, "middle"), new Student(1, "young"), new Student(10, "old") ); System.out.println(list.toString()); // [middle, young, old] Collections.sort(list); System.out.println(list.toString()); // [young, middle, old] }}public class Student implements Comparable<Student> { int age; String name; public Student(int age, String name) { this.age = age; this.name = name; } @Override public int compareTo(Student o) { return age - o.age; } @Override public String toString() { return name; }}
stdout
[middle, young, old]
[young, middle, old]
Functional Interfaces
It is often useful to be able to pass some logic into a function. Especially when it comes to sorting.
Perhaps the objects I am sorting have no natural order (doesn’t implement Comparable<T>), or I want to define my own custom order!
This is where Functional Interfaces come into play! Unlike Behavioural Interfaces, these interfaces are used solely to pass functions around. They should not be implemented by a Class, as they are meant to represent a function on its own, and not a behaviour that a Class can exhibit.
Instead, they should be implemented with the lambda syntax or method reference.
The Functional Interface from the Java standard library Comparator<T> is what is used for implementing a custom sorting order.
This Functional Interfaces defines the method int compare(T o1, o2) which compares two objects of the same Class/type, allowing us to define the order of objects of a Class Studentseparately from the code in that Class.
The Collections Class in Java, also defines anothermethod named sort which has the following signature:
public static <T> void sort(List<T> list, Comparator<? super T> c)
This is also a generic method using generic wildcards. However, instead of restricting the generic T to be of a type that has natural order (implements Comparable).
This method allows us to pass in a List<T> list of any type, but we must also supply Comparator<? super T> c, which is an implementation of Comparator<T> that is compatible with the type of the elements in the List<T> list.
So, by supplying an implementation of Comparator<T> to Collections.sort(List<T>, Comparator<? super T> c) we can sort any list of any elements in any way we want, even if the element itself is not of a type that has natural order.
Sample Compartor<T> Code
import java.util.Arrays;import java.util.Collections;import java.util.List;public class Main { public static void main(String[] args) { List<Student> list = Arrays.asList( new Student(5, "middle"), new Student(1, "young"), new Student(10, "old") ); System.out.println(list.toString()); // [middle, young, old] // Pass in an instance of Comparator that orders Students based on the length of their name Collections.sort(list, (o1, o2) -> o1.name.length() - o2.name.length()); System.out.println(list.toString()); // [old, young, middle] }}public class Student implements Comparable<Student> { int age; String name; public Student(int age, String name) { this.age = age; this.name = name; } @Override public int compareTo(Student o) { return age - o.age; } @Override public String toString() { return name; }}