Menu

Interfaces and Multiple Inheritance in Java

Interfaces and Multiple Inheritance

Chapter 7 of the Beginner tutorial introduced interfaces as pure contracts. Since Java 8, interfaces have grown considerably more powerful, and they remain Java's main route to something resembling multiple inheritance. This chapter explores both of those ideas, ending with the feature that interfaces ultimately made possible: lambda expressions.

Implementing Multiple Interfaces

Java does not allow a class to extend more than one class, to avoid the ambiguity of inheriting conflicting implementations. But a class can implement any number of interfaces at once, since interfaces traditionally provided no implementation to conflict over, only a contract to fulfil.

interface Swimmer {

void swim();

}

interface Flyer {

void fly();

}

class Duck implements Swimmer, Flyer {

@Override

public void swim() {

System.out.println("Duck is swimming");

}

@Override

public void fly() {

System.out.println("Duck is flying");

}

}

Duck now satisfies two completely independent contracts at once, something a single-inheritance extends relationship could never achieve. This is exactly the gap that multiple interface implementation exists to fill.

Default and Static Methods in Interfaces (Java 8+)

Before Java 8, every interface method was implicitly abstract; no body allowed. Java 8 introduced default methods, which do carry an implementation, giving implementing classes a ready-made behaviour they can use as-is, or override if they need something different.

interface Vehicle {

void drive();

default void honk() {

System.out.println("Beep beep!");   // default implementation, ready to use

}

static String describe() {

return "A Vehicle interface";       // static methods belong to the interface itself

}

}

class Car implements Vehicle {

@Override

public void drive() {

System.out.println("Car is driving");

}

// honk() is inherited as-is — no need to implement it

}

Car car = new Car();

car.honk();                       // Beep beep! — using the interface's default

System.out.println(Vehicle.describe());   // called directly on the interface

Default methods exist mainly to let an interface evolve over time without breaking every class that already implements it: a new default method can be added to a widely-used interface, and existing implementing classes keep compiling, automatically picking up the new behaviour without any changes of their own.

  • If a class implements two interfaces that each provide a default method with the exact same signature, the class must override that method itself to resolve the conflict; Java will not guess which one to use.
  • Static interface methods cannot be inherited by implementing classes; they must always be called directly on the interface name, as shown with Vehicle.describe() above.

Functional Interfaces and Lambda Expressions

A functional interface is simply an interface with exactly one abstract method, regardless of how many default or static methods it also has. Having only a single abstract method means an implementation of it is, conceptually, just a single chunk of behaviour, which is precisely what a lambda expression is built to express.

@FunctionalInterface

interface Calculator {

int operate(int a, int b);

}

public class Main {

public static void main(String[] args) {

// Old style — anonymous inner class

Calculator oldStyle = new Calculator() {

@Override

public int operate(int a, int b) {

return a + b;

}

};

// Modern style — lambda expression

Calculator add = (a, b) -> a + b;

Calculator multiply = (a, b) -> a * b;

System.out.println(add.operate(3, 4));      // 7

System.out.println(multiply.operate(3, 4)); // 12

}

}

The @FunctionalInterface annotation is optional, but writing it is good practice: it tells the compiler to verify the interface truly has only one abstract method, catching an accidental second method early instead of producing a confusing error wherever the lambda is eventually used.

Java ships with a set of ready-made functional interfaces in java.util.function, so you rarely need to declare your own for common cases.

Interface

Abstract Method

Typical Use

Runnable

void run()

A block of code to execute, with no input or output

Supplier<T>

T get()

Produces a value, taking no input

Consumer<T>

void accept(T t)

Takes one input and performs an action, returns nothing

Function<T,R>

R apply(T t)

Transforms an input of type T into an output of type R

Predicate<T>

boolean test(T t)

Evaluates an input and returns true or false

import java.util.function.Predicate;

Predicate<Integer> isEven = n -> n % 2 == 0;

System.out.println(isEven.test(10));   // true

System.out.println(isEven.test(7)); // false

Did You Know?

Lambda expressions did not give Java a new kind of inheritance or a new way to create objects. Under the hood, a lambda still creates an object that implements a functional interface, exactly like the anonymous inner class shown above. The lambda syntax is essentially a much more concise way of writing the same thing, which is part of why it slots so naturally into a language built around interfaces.