Interfaces
Many concepts in Java are derived from real-world scenarios. Interfaces as well. If you want to turn on a television, you need to press the power button. This button is the interface between you and the electronics inside the television. Java interfaces have the same behaviour as real-world interfaces (e.g., a television interface): they set strict rules (a blueprint, if you will) on how to interact with objects. As you have already learned, these strict rules can be defined with methods. In fact, Java objects define their interaction with the outside world through the methods that they expose.
In a nutshell, an Interface in Java is an abstract type used to specify how the outside world can interact with the objects that implement the interface.
How to create an Interface?
You create them as you create classes, but you must use the keyword interface instead of class.
Going back to our example with the television, let’s create an Interface called Television with two methods pressOnButton() and pressOffButton()
public interface Television {
public void pressOnButton();
public void pressOffButton();
}
The methods pressOnButton(); pressOffButton(); have no body (i.e., no {}), they just end with ;, they are by default abstract. In a nutshell, a Java interface represents a group of (abstract) methods with no bodies.
How to implement an Interface?
Now, we will create a class that implements our interface. Yes, a class does not extends an interface (like we did with classes), it implements it. For example, let’s create a class LCDTelevision.
public class LCDTelevision implements Television {
@Override
public void pressOnButton() {
System.out.println("The television is on");
}
@Override
public void pressOffButton() {
System.out.println("The television is off");
}
}
We use the implements keyword in the class declaration. The class gives a compilation error if we do not override the methods declared in the interface Television. The interface Television gives the methods that all the classes that implement a Television must have. In our example, the pressOnButton()and pressOffButton() methods.
The relationship between the interface and the class that implements it can be visualised with a UML-style diagram. The dashed arrow with the open triangle means implements (an interface is drawn with the <<interface>> stereotype above its name):
In UML, a solid arrow with an open triangle means extends (class inheritance), and a dashed arrow with an open triangle means implements (the relationship between a class and an interface it implements). You will see this notation throughout the course and in most Java documentation.
Now, if we want to instantiate an object LCDTelevision we can do:
public static void main(String[] args) {
Television lcdTv = new LCDTelevision();
lcdTv.pressOnButton();
System.out.println("I am watching TV....");
lcdTv.pressOffButton();
}
Note that, interfaces like abstract classes cannot be instantiated (we cannot do Television tv = new Television(); ) we must specify the implementation we want: Television lcdTv = new LCDTelevision()).
A note on code style
Please note that the declared type of lcdTv is Television not LCDTelevision. We could have also used LCDTelevision as the type of lcdTv(i.e., LCDTelevision lcdTv = new LCDTelevision();). This would have worked fine.
However, depending on the situation, often it might be handy to store the reference of an object that implements an interface I with a variable of type I. This often leads to more maintainable code.
For example, it is considered good style to store a reference to a JDK ArrayList or LinkedList in a variable of type List. List is a common interface of both ArrayList and LinkedList.
public static void main(String[] args) {
List<String> names = new ArrayList<String>();
createNames(names);
}
public static void createNames(List<String> names){
// some code
}
In this way, if later you decide to use LinkedList instead of an ArrayList you just need to change one line of code: List<String> names = new ArrayList<String>(); to List<String> names = new LinkedList<String>();. You do not need to change other lines of code that use the list names. For example, you do not need to change the method declaration of method createNames. Conversely, you must do it if the object’s reference was of type ArrayList.
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<String>();
createNames(names);
}
public static void createNames(ArrayList<String> names){
// some code
}
Note that, in both cases: List<String> names = new ArrayList<String>(); and ArrayList<String> names = new ArrayList<String>();, the names reference variable points to an object of type ArrayList. It is only the type of the reference that is in one case List and in the other ArrayList.
What is the difference between an abstract class and an interface?
I believe that right now, you are wondering what is the difference between an abstract class and an interface. You can think about interfaces as being “a really really abstract abstract class. So abstract that it’s no longer a class – it’s now just an interface“. Historically an interface could not contain any implementation at all; since Java 8, interfaces can provide a limited form of implementation via default and static methods (we will see these shortly). A full comparison table is provided at the end of this lecture as a reference, once you have seen interfaces in action.
One of the key differences between interfaces and abstract classes is that you can implement more than one interface in one class (you cannot do this with abstract classes). You just need to separate the interface names with commas in the class declaration. For example, let’s say that we have an interface ElectricalAppliance defined as:
public interface ElectricalAppliance {
public void plugIn();
public void plugOut();
}
Then, our LCDTelevision can implement both interfaces: ElectricalAppliance and Television. Note that in this case, the LCDTelevision class has to provide the implementation of all four methods: pressOnButton, pressOffButton, plugIn, and plugOut.
public class LCDTelevision implements Television, ElectricalAppliance {
@Override
public void pressOnButton() {
System.out.println("The television is on");
}
@Override
public void pressOffButton() {
System.out.println("The television is off");
}
@Override
public void plugIn() {
System.out.println("The television is plugged into an electricity supply");
}
@Override
public void plugOut() {
System.out.println("The television is unplugged from the electricity supply");
}
}
Why does Java need interfaces to support “multiple inheritance”? Why can’t a class extend multiple classes?
Java does not support “multiple inheritance” (a class can only inherit from one superclass). However, “multiple inheritance” can be achieved with interfaces: a class can implement multiple interfaces.
You might wonder why multiple inheritance is possible with interfaces but not with classes? The answer is because of ambiguity. Two classes might have different implementations of the same method or might have two different functionalities with the same method name. This ambiguity is famously known as the diamond problem: if class D inherited from both B and C, and both B and C inherited the same method from a common parent A (or defined it independently), the JVM would not know which version D should use. The inheritance graph forms a “diamond” shape — hence the name.
For example, let’s assume that Java allows multiple inheritance with classes.
class A {
public void run() {
System.out.println("A");
}
}
class B {
public void run() {
System.out.println("B");
}
}
public class MyClass extends A, B { // ATTENTION extending two classes is not allowed
// we do it just for making a point :)
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.run(); // Which method should I execute?
}
}
Should the program print “A” or “B”? The JVM would not be able to decide which method to execute. There is ambiguity. However, there is no ambiguity if we use interfaces. It is because the implementation of the methods is never specified in interfaces. Interfaces specify only what the class is doing, not how it is doing it. How it is doing it must be specified by the class that implements the interface. For example, if we use interfaces, even if two interfaces have the same method names, there is no conflict or ambiguity. This is because it is MyClass that defines the implementation.
interface A {
public void run();
}
interface B {
public void run();
}
public class MyClass implements A, B {
@Override
public void run() {
System.out.println("A");
}
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.run();
}
}
Can I have an “empty” interface that does not declare any methods?
Yes, you can! Is it something useful? Yes, indeed. For example, let’s say that we want to create a List that contains Vehicles. Vehicles can be very different so I don’t expect that they can share functionalities. So we can use interfaces to just tell the JVM that the List can only contain Vehicle objects.
import java.util.ArrayList;
import java.util.List;
interface Vehicle {
// empty interface
}
class Car implements Vehicle {
public void drive(int speed) {
// some implementation
}
}
class Airplane implements Vehicle {
public void fly(int speed, int altitude) {
// some implementation
}
}
class Boat implements Vehicle {
public void sail(int direction, int speed) {
// some implementation
}
}
public class MyClass {
public static void main(String[] args) {
List<Vehicle> vehicles = new ArrayList<Vehicle>();
vehicles.add(new Car());
vehicles.add(new Airplane());
vehicles.add(new Boat());
}
}
Can an interface inherit from another interface?
Yes, but instead of using implements you will use extends. In fact: a class extends another class, an interface extends another interface, but a class implements an interface.
For example you can do:
interface A {
public void funcA();
}
interface B extends A {
public void funcB();
}
class C implements B {
@Override
public void funcA() {
System.out.println("Implementation function A");
}
@Override
public void funcB() {
System.out.println("Implementation function B");
}
}
In this case, the class C must implement both method funcA and funcB.
default methods (Java 8+)
So far, every method declared in an interface has had no body, and every class that implements the interface has been forced to provide an implementation. This is restrictive: if you add a new method to an interface, every existing class that implements it would break (compile error: “does not override abstract method”). This was a real problem when the Java team wanted to add new methods (for example, stream()) to existing interfaces like Collection without breaking the millions of classes that already implemented them.
Since Java 8, an interface can provide a default implementation for a method using the default keyword. Classes that implement the interface can override it, but they don’t have to — they inherit the default for free.
public interface Television {
public void pressOnButton();
public void pressOffButton();
// default method: classes that implement Television get this for free
default void pressMuteButton() {
System.out.println("The television is muted (default behaviour)");
}
}
A class that implements Television does not need to override pressMuteButton() — it can, if it wants to customise the behaviour, but if it doesn’t, the default is used:
public class LCDTelevision implements Television {
@Override public void pressOnButton() { System.out.println("The television is on"); }
@Override public void pressOffButton() { System.out.println("The television is off"); }
// pressMuteButton() is inherited from the interface's default implementation
}
Key takeaway: default methods let interfaces evolve over time without breaking the code of existing implementers. They are the reason interfaces today are more than just pure “contracts” — they can carry a small amount of shared behaviour, too.
static methods in interfaces (Java 8+)
An interface can also declare static methods — methods that belong to the interface itself, not to any implementing object. These are typically used to provide factory methods or utility helpers related to the interface.
public interface Television {
public void pressOnButton();
public void pressOffButton();
// static factory method: called on the interface, not on an instance
static Television defaultModel() {
return new LCDTelevision();
}
}
// Usage:
Television tv = Television.defaultModel();
tv.pressOnButton();
Note: Since Java 9, interfaces can also declare
privatemethods, used as helpers shared betweendefaultmethods inside the same interface. You will rarely need them in this course, but it’s useful to know they exist.
Program to an interface, not an implementation
This is one of the most important design principles in object-oriented programming — and you have already seen it in action in the code-style box earlier in this lecture, when we preferred List<String> names = new ArrayList<>(); over ArrayList<String> names = new ArrayList<>();.
“Program to an interface, not an implementation.”
— Design Patterns, Gamma, Helm, Johnson, Vlissides (1994)
The idea is simple: whenever a variable, parameter, or return type could be declared as an interface instead of a concrete class, prefer the interface. This decouples the code that uses an object from the code that produces it: you can swap the implementation later (ArrayList → LinkedList, HashMap → TreeMap, LCDTelevision → OLEDTelevision) without touching any of the surrounding code.
// GOOD — the method works with any List implementation
public void printAll(List<String> items) { ... }
// BAD — the method is tied to ArrayList; a LinkedList cannot be passed in
public void printAll(ArrayList<String> items) { ... }
The same principle applies to the return type of a method: if you return List<String>, you are free to change how the list is built internally without breaking any caller. If you return ArrayList<String>, every caller becomes locked to that choice.
(Summary) Key Facts about Interfaces:
- Interface is a fundamental concept in Java OOP. It is used to achieve abstraction and multiple inheritance.
- An interface typically represents a CAN-DO relationship (a capability), rather than the IS-A relationship expressed by
extends. For example, aBirdis aAnimal(inheritance), but aBirdcanFly(interface capability — and so can aPlane, which is not anAnimal). - Like abstract classes, interfaces cannot be used to create objects (in the example above, it is not possible to instantiate a “Television” object).
- By default, interface methods have no body — the body is provided by the class that implements the interface.
- Since Java 8, interfaces can also contain
defaultmethods (with a body, inherited for free by implementers) andstaticmethods (called on the interface itself). Since Java 9, they can containprivatehelper methods. - An interface cannot contain a constructor (as it cannot be used to create objects).
- If a class implements an interface, it must implement all of the interface’s abstract methods (it does not need to override
defaultmethods, unless it wants to customise them). - Interface methods are by default
abstractandpublic. - Interface attributes are by default
public,staticandfinal— i.e. interfaces can only hold constants, never instance state. - Program to an interface, not an implementation: prefer declaring variables, parameters, and return types using the interface type (e.g.
List) rather than the concrete class (ArrayList).
Get your hands dirty!
You are hiking in the forest, and you come across interesting items that you are allowed to gather and put in your backpack. Some items are eatable, some are not.
import java.util.ArrayList;
import java.util.List;
public class Mushroom {
private boolean isPoisonous() {
System.out.println("check online if it is poisonous...");
return false; // let's assume it is not
}
public void eat() {
if (!isPoisonous()) {
System.out.println("eating the mushroom");
}
}
}
public class Apple {
private void peel() {
System.out.println("peeling the apple");
}
public void eat() {
peel();
System.out.println("eating the apple");
}
}
public class Strawberry {
private void wash() {
System.out.println("wash the strawberry");
}
public void eat() {
wash();
System.out.println("eating the strawberry");
}
}
public class Stone {
public void examine() {
System.out.println("examining the stone");
}
}
public class WoodenStick {
public void use() {
System.out.println("use wooden stick to walk");
}
}
After a while, you feel hungry, and you want to eat all the eatable objects that you have found.
public class Main {
public static void main(String[] args) {
List<Object> backpack = new ArrayList<Object>();
System.out.println("Walking in the forest...");
System.out.println("I found a beautiful stone...");
backpack.add(new Stone());
System.out.println("I found a bush with 10 strawberries");
for (int i = 0; i < 10; i++) {
backpack.add(new Strawberry());
}
System.out.println("I found a wooden stick");
backpack.add(new WoodenStick());
System.out.println("I found 3 mushrooms and 1 apple");
backpack.add(new Mushroom());
backpack.add(new Mushroom());
backpack.add(new Mushroom());
backpack.add(new Apple());
System.out.println("another beautiful stone...");
backpack.add(new Stone());
System.out.println("I am tired and hungry");
System.out.println();
System.out.println(" I want to eat all the eatable items that I found");
for (Object item : backpack) {
if (item instanceof Strawberry) {
((Strawberry) item).eat();
} else if (item instanceof Apple) {
((Apple) item).eat();
} else if (item instanceof Mushroom) {
((Mushroom) item).eat();
}
}
}
}
The problem with this implementation is that if later on, we find new types of eatable objects (blueberries, walnuts, … ) we need to add other else if conditions that would make the code verbose and difficult to maintain and modify. Let’s use interfaces to improve the design of our program!
Edit the Repl, which contains the code above, as follows:
- Create one interface
FoundObject, all the classes (regardless if they represent eatable objects or not) should implement this interface. Should this interface have methods? Do all the classes have the same functionalities? - Change the type of the list
backpackaccordingly - Create another interface specific to eatable objects:
Eatable. Classes that represent eatable objects should also implement this interface. Is there any functionality common to all Eatable objects? - Change the body of the
for loop. Still, we should eat only eatable objects. Use exactly oneinstanceofcheck to filter only the eatable items — notice that you no longer need a chain ofif/else iffor each concrete class. - Now go one step further: can you remove
instanceofentirely? (HINT: if thebackpackheld onlyEatableitems, the loop would not need any type check. What would the trade-off be?). This is the key win of polymorphism through interfaces: the caller does not need to know which concrete class it is dealing with. - BONUS Can you modify the program so that we still have two interfaces but
EatableextendsFoundObject? In this case, does the classAppleneed to implement bothEatableandFoundObjectinterfaces? - BONUS Can you modify the program so that
FoundObjecthas now aweightfield?, should you still use interfaces? or now is it better to use abstract classes?
Reference: Interface vs. Abstract class
Now that you have used interfaces in the hands-on exercise, the trade-offs with abstract classes should feel more concrete. The table below summarises the key differences (all rows reflect modern Java — Java 21 LTS and later).
| Property | Interface | Abstract class |
|---|---|---|
| Multiple inheritance | A class can implement several interfaces. | A class can extend only one abstract class. |
| Can contain (methods) | abstract methods, default methods (Java 8+), static methods (Java 8+), and private helper methods (Java 9+). |
abstract methods and/or concrete (implemented) methods, static methods, final methods. |
| Can contain (fields / state) | Only constants — every field is implicitly public static final. An interface cannot hold instance state. |
Any fields: instance fields, static fields, with any visibility. |
| Constructor | Not allowed — an interface has no state to initialise. | Allowed — called via super(...) by concrete subclasses. |
| Method visibility | abstract, default, and static methods are implicitly public. Helper methods may be private (Java 9+). |
Any visibility: public, protected, package-private, private. |
| Inheritance between types | An interface can extends multiple interfaces; it cannot extend a class. |
An abstract class can extends one class and implements multiple interfaces. |
| Relationship modelled | "CAN-DO" — a capability shared by otherwise unrelated classes (e.g. Runnable, Comparable, Eatable). |
"IS-A" — a kind-of relationship where subclasses share both behaviour and state (e.g. AbstractList is a List). |
| When to use | When unrelated classes should share a capability, and you mostly need to fix method signatures (plus optional small default behaviours). |
When related classes share real behaviour and/or state you want to factor out in one place. |
