Interfaces
IB Syllabus: B3.2 (HL) – Use interfaces to define contracts that classes must implement.
HL Only – This page covers content assessed at HL level only.
Table of Contents
- Key Concepts
- Worked Examples
- Quick Check
- Trace Exercise
- Spot the Error
- Predict the Output
- Practice Exercises
- Connections
Key Concepts
What is an Interface?
An interface defines a contract – a set of method signatures that any implementing class must provide. It specifies what a class can do, without specifying how it does it.
public interface Drawable {
void draw();
String getDescription();
}
An interface contains only:
- Abstract method signatures (no method bodies)
- Constants (
public static finalby default)
It contains no constructors, no instance variables, and no method implementations.
Implementing an Interface
A class uses the implements keyword to adopt an interface. It must then provide a body for every method declared in the interface:
public class Circle implements Drawable {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Drawing a circle with radius " + radius);
}
@Override
public String getDescription() {
return "Circle (r=" + radius + ")";
}
}
public class TextBox implements Drawable {
private String text;
public TextBox(String text) {
this.text = text;
}
@Override
public void draw() {
System.out.println("Rendering text: " + text);
}
@Override
public String getDescription() {
return "TextBox: " + text;
}
}
If a class says implements Drawable but does not provide draw() and getDescription(), the compiler reports an error.
An interface is a promise. When a class implements an interface, it guarantees it will provide all the methods the interface declares. Other code can rely on this guarantee without knowing the specific class.
Interface as a Type
Just like abstract classes, interfaces can be used as variable types, parameter types, and array types:
Drawable[] canvas = new Drawable[3];
canvas[0] = new Circle(5);
canvas[1] = new TextBox("Hello");
canvas[2] = new Circle(3);
for (int i = 0; i < canvas.length; i++) {
System.out.println(canvas[i].getDescription());
canvas[i].draw();
}
Output:
Circle (r=5.0)
Drawing a circle with radius 5.0
TextBox: Hello
Rendering text: Hello
Circle (r=3.0)
Drawing a circle with radius 3.0
The loop does not know or care whether each element is a Circle or a TextBox. It only knows each element is Drawable and therefore has draw() and getDescription().
Multiple Interfaces
A class can implement multiple interfaces. This is a key advantage over inheritance, where a class can only extend one parent:
public interface Printable {
void print();
}
public interface Saveable {
void save(String filename);
}
public class Document implements Printable, Saveable {
private String content;
public Document(String content) {
this.content = content;
}
@Override
public void print() {
System.out.println("Printing: " + content);
}
@Override
public void save(String filename) {
System.out.println("Saving to " + filename);
}
}
Document implements both Printable and Saveable. It can be stored in a Printable[] array OR a Saveable[] array, depending on context. This flexibility is impossible with single inheritance alone.
Java allows single inheritance (one
extends) but multiple interface implementation (manyimplements). This is how Java provides the benefits of multiple inheritance without the complexity.
Combining Interfaces and Inheritance
A class can extend a parent class AND implement one or more interfaces at the same time:
public abstract class Shape {
private String colour;
public Shape(String colour) {
this.colour = colour;
}
public String getColour() { return colour; }
public abstract double getArea();
}
public class Circle extends Shape implements Drawable {
private double radius;
public Circle(String colour, double radius) {
super(colour);
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
@Override
public void draw() {
System.out.println("Drawing " + getColour() + " circle");
}
@Override
public String getDescription() {
return getColour() + " circle (r=" + radius + ")";
}
}
Circle inherits from Shape (getting colour and requiring getArea()) and implements Drawable (requiring draw() and getDescription()). The extends clause comes before implements.
Interface vs Abstract Class
| Feature | Abstract Class | Interface |
|---|---|---|
| Keyword | abstract class | interface |
| Adoption | extends (one only) | implements (many allowed) |
| Constructors | Yes | No |
| Instance variables | Yes | No (constants only) |
| Concrete methods | Yes (shared behaviour) | No (only signatures) |
| Abstract methods | Yes (some or all) | All methods are abstract |
| When to use | Classes share common state and behaviour | Unrelated classes share a capability |
When to use an abstract class:
- The subclasses share attributes and concrete methods (e.g.,
Animalwithname,age,getName()) - There is a clear is-a hierarchy
When to use an interface:
- Multiple unrelated classes need the same capability (e.g.,
Drawable,Comparable,Serializable) - A class needs to conform to multiple contracts
Think of abstract classes as “what something IS” and interfaces as “what something CAN DO.” A
DogIS anAnimal(abstract class), but aDogCAN beTrainable(interface).
Worked Examples
Example 1: Comparable Items
A shop needs to sort products and students by different criteria. Both can be compared, but they are completely unrelated classes.
public interface Sortable {
int getSortKey();
}
public class Product implements Sortable {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public double getPrice() { return price; }
@Override
public int getSortKey() {
return (int) (price * 100); // sort by price in cents
}
}
public class Student implements Sortable {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() { return name; }
public int getScore() { return score; }
@Override
public int getSortKey() {
return score; // sort by score
}
}
A single sort method works for ANY Sortable array:
// Selection sort that works on ANY Sortable objects
public static void sort(Sortable[] items) {
for (int i = 0; i < items.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < items.length; j++) {
if (items[j].getSortKey() < items[minIndex].getSortKey()) {
minIndex = j;
}
}
Sortable temp = items[minIndex];
items[minIndex] = items[i];
items[i] = temp;
}
}
Product[] products = {
new Product("Laptop", 999.99),
new Product("Mouse", 29.99),
new Product("Keyboard", 79.99)
};
sort(products);
for (int i = 0; i < products.length; i++) {
System.out.println(products[i].getName() + ": $" + products[i].getPrice());
}
Output:
Mouse: $29.99
Keyboard: $79.99
Laptop: $999.99
The sort() method does not know about Product or Student. It only relies on the Sortable interface contract.
Example 2: Multiple Interfaces
A smart home system where devices can be both controllable and monitorable.
public interface Controllable {
void turnOn();
void turnOff();
boolean isOn();
}
public interface Monitorable {
String getStatus();
}
public class SmartLight implements Controllable, Monitorable {
private String room;
private boolean on;
public SmartLight(String room) {
this.room = room;
this.on = false;
}
@Override
public void turnOn() { on = true; }
@Override
public void turnOff() { on = false; }
@Override
public boolean isOn() { return on; }
@Override
public String getStatus() {
return room + " light: " + (on ? "ON" : "OFF");
}
}
public class Thermostat implements Controllable, Monitorable {
private double temperature;
private boolean on;
public Thermostat(double temperature) {
this.temperature = temperature;
this.on = false;
}
@Override
public void turnOn() { on = true; }
@Override
public void turnOff() { on = false; }
@Override
public boolean isOn() { return on; }
@Override
public String getStatus() {
String state = on ? "heating to " + temperature + "C" : "OFF";
return "Thermostat: " + state;
}
}
Controllable[] devices = {
new SmartLight("Kitchen"),
new Thermostat(22.0)
};
// Turn everything on
for (int i = 0; i < devices.length; i++) {
devices[i].turnOn();
}
// Check status (need to cast to Monitorable)
for (int i = 0; i < devices.length; i++) {
Monitorable m = (Monitorable) devices[i];
System.out.println(m.getStatus());
}
Output:
Kitchen light: ON
Thermostat: heating to 22.0C
Both SmartLight and Thermostat implement two interfaces. They can be treated as Controllable (to turn on/off) or Monitorable (to check status), depending on what the code needs.
Quick Check
Q1. What does an interface contain?
Q2. How many interfaces can a single class implement?
Q3. A class implements Drawable but does not provide a draw() method. What happens?
Q4. When should you use an interface instead of an abstract class?
Q5. What is the correct syntax for extending a class AND implementing an interface?
Trace Exercise
Trace the interface method calls. SmartLight and Thermostat both implement Controllable and Monitorable.
Trace: Interface Dispatch
SmartLight light = new SmartLight("Living Room");
Thermostat thermo = new Thermostat(21.5);
light.turnOn();
thermo.turnOn();
thermo.turnOff();| Device | isOn() | getStatus() |
|---|---|---|
| light | ||
| thermo |
The thermostat was turned on, then turned off again. Its final state is off.
Spot the Error
This code defines an interface and a class that should implement it. Find the error.
Bug Hunt: A media player interface. The AudioPlayer class should implement all methods from Playable, which declares play(), stop(), and pause().
Pick the correct fix for line 8:
Predict the Output
Using the Drawable interface from Key Concepts, where Circle.getDescription() returns "Circle (r=" + radius + ")" and TextBox.getDescription() returns "TextBox: " + text. What does this print?
Drawable[] canvas = new Drawable[2];
canvas[0] = new Circle(5);
canvas[1] = new TextBox("Hello");
System.out.println(canvas[0].getDescription());
System.out.println(canvas[1].getDescription());Type both lines separated by \n:
Practice Exercises
Core
-
Measurable interface – Create an interface
Measurablewith a methoddouble getMeasurement(). Create two classes:Ruler(returns its length in cm) andThermometer(returns the current temperature). Store both in aMeasurable[]array and print each measurement. -
Interface type – Given
Drawable[] items = new Drawable[3];, explain whyitems[0] = new Drawable();would cause a compile error, butitems[0] = new Circle(5);works (assuming Circle implements Drawable).
Extension
-
Two interfaces – Create interfaces
Readable(withString read()) andWritable(withvoid write(String data)). Create aFileclass that implements both. Create aKeyboardclass that implements onlyReadable. Demonstrate using the interface types for polymorphic arrays. -
Compare and contrast – Rewrite the abstract
Shapeclass from the Abstract Classes page as an interface calledShapeSpecinstead. What do you lose? What do you gain? Which approach is better for shapes, and why?
Challenge
- Plugin system – Design an interface
Pluginwith methodsString getName(),void execute(), andint getPriority(). Create three concrete plugins:SpellChecker,WordCounter, andAutoSaver. Write aPluginManagerclass that stores plugins in an array, sorts them by priority (lowest first), and executes them in order.
Connections
- Prerequisites: Abstract Classes – interfaces take abstraction further
- Prerequisites: Polymorphism – interfaces enable polymorphism across unrelated classes
- Next: Composition vs Inheritance – has-a vs is-a, tight coupling, favour composition
- Related: Encapsulation – interfaces enforce encapsulation by hiding implementation
- Related: UML Class Diagrams – interfaces shown with dashed arrows in UML