Skip to content
Laviyx Learn

Try

Java course menu

Java Interfaces: implements, Default Methods and Functional Interfaces

Learn Java interfaces: declaring and implementing interfaces, programming to an interface, implementing several interfaces, default and static methods, constants, functional interfaces for lambdas, sealed interfaces, and interface vs abstract class.

Intermediate14 min readUpdated
On this page

An interface describes a set of abilities that a class promises to provide, without saying how. Any class can implement an interface, and a class can implement many interfaces. Interfaces are everywhere in Java: List, Map, Comparable and Runnable are all interfaces.

Declaring and implementing an interface#

Java
import java.util.List;

interface Payment {
    boolean pay(double amount);
    String method();
}

class CardPayment implements Payment {
    @Override
    public boolean pay(double amount) {
        return amount <= 50_000;
    }

    @Override
    public String method() {
        return "card";
    }
}

class UpiPayment implements Payment {
    @Override
    public boolean pay(double amount) {
        return amount <= 100_000;
    }

    @Override
    public String method() {
        return "UPI";
    }
}

public class Main {
    public static void main(String[] args) {
        List<Payment> options = List.of(new CardPayment(), new UpiPayment());
        for (Payment p : options) {
            System.out.println(p.method() + " can pay 75000: " + p.pay(75_000));
        }
    }
}
Output
card can pay 75000: false
UPI can pay 75000: true
  • Interface methods are public and abstract by default, so implementing methods must be public.
  • implements Payment is a promise: the compiler checks that the class provides every method.

Program to an interface#

Declaring variables, parameters and return types with the interface type, rather than a specific class, keeps code flexible:

Java
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Main {
    static int countLong(List<String> words) {   // accepts ANY List implementation
        int count = 0;
        for (String w : words) {
            if (w.length() > 4) count++;
        }
        return count;
    }

    public static void main(String[] args) {
        List<String> a = new ArrayList<>(List.of("apple", "fig", "banana"));
        List<String> b = new LinkedList<>(List.of("kiwi", "cherry"));
        System.out.println(countLong(a) + " " + countLong(b));
    }
}
Output
2 1

That is why Java code usually writes List<String> list = new ArrayList<>();: the rest of the code depends only on List, so the implementation can change in one place.

Implementing several interfaces#

A class extends one class, but can implement any number of interfaces:

Java
interface Printable {
    String toText();
}

interface Saveable {
    boolean save();
}

class Invoice implements Printable, Saveable {
    private final int number;

    Invoice(int number) { this.number = number; }

    @Override
    public String toText() { return "Invoice #" + number; }

    @Override
    public boolean save() {
        System.out.println("saving " + toText());
        return true;
    }
}

public class Main {
    static void print(Printable item) {
        System.out.println(item.toText());
    }

    public static void main(String[] args) {
        Invoice inv = new Invoice(1042);
        print(inv);
        inv.save();
    }
}
Output
Invoice #1042
saving Invoice #1042

print accepts anything printable and knows nothing about invoices.

Default and static methods#

Since Java 8, interfaces can contain:

  • default methods: methods with a body that implementing classes inherit (and may override). They let an interface grow without breaking existing implementations.
  • static methods: helpers called on the interface name.
Java
interface Greeter {
    String name();

    default String greet() {                 // inherited by every implementation
        return "Hello, " + name() + "!";
    }

    static Greeter of(String name) {          // a factory method
        return () -> name;
    }
}

class Robot implements Greeter {
    @Override public String name() { return "R2"; }
    @Override public String greet() { return "BEEP. " + name() + " ONLINE."; }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(Greeter.of("Asha").greet());
        System.out.println(new Robot().greet());
    }
}
Output
Hello, Asha!
BEEP. R2 ONLINE.

() -> name is a lambda: because Greeter has exactly one abstract method, a lambda can implement it. That leads to the next section.

Functional interfaces#

An interface with exactly one abstract method is a functional interface, and can be implemented with a short lambda instead of a whole class. The annotation @FunctionalInterface asks the compiler to check this:

Java
@FunctionalInterface
interface Operation {
    double apply(double a, double b);
}

public class Main {
    static double calculate(double a, double b, Operation op) {
        return op.apply(a, b);
    }

    public static void main(String[] args) {
        Operation add = (a, b) -> a + b;
        Operation power = Math::pow;              // a method reference

        System.out.println(calculate(8, 2, add));
        System.out.println(calculate(8, 2, (a, b) -> a * b));
        System.out.println(calculate(2, 10, power));
    }
}
Output
10.0
16.0
1024.0

The standard library provides ready-made functional interfaces in java.util.function, such as Predicate, Function and Consumer. They are covered in Lambdas and Functional Interfaces.

Constants in interfaces#

Fields declared in an interface are automatically public static final constants. They work, but a dedicated class or an enum is usually a clearer home for constants.

Sealed interfaces (Java 17)#

A sealed interface lists exactly which classes may implement it. The compiler then knows every possible type, which pairs perfectly with switch pattern matching:

Java
sealed interface Shape permits Circle, Square {}
record Circle(double r) implements Shape {}
record Square(double side) implements Shape {}

public class Main {
    static double area(Shape s) {
        return switch (s) {          // no default needed: the compiler knows every case
            case Circle c -> Math.PI * c.r() * c.r();
            case Square q -> q.side() * q.side();
        };
    }

    public static void main(String[] args) {
        System.out.printf("%.2f %.2f%n", area(new Circle(1)), area(new Square(3)));
    }
}
Output
3.14 9.00

You will learn about record in the next lesson.

Interface or abstract class?#

Use an interface to describe a capability (Comparable, Payment, Printable) that unrelated classes can share, and as the type in your variables and parameters. Use an abstract class when closely related classes share state and code. When in doubt, start with an interface.

Common mistakes#

Practice#

Practice exercise

Define an interface Shape with double area() and a default method String describe() that returns "Area: <area>" with two decimals. Implement it with Rectangle and Circle, then print the description of a 4 × 2.5 rectangle and a circle of radius 1.

Show solution
Java
interface Shape {
    double area();

    default String describe() {
        return String.format("Area: %.2f", area());
    }
}

class Rectangle implements Shape {
    private final double w;
    private final double h;
    Rectangle(double w, double h) { this.w = w; this.h = h; }
    @Override public double area() { return w * h; }
}

class Circle implements Shape {
    private final double r;
    Circle(double r) { this.r = r; }
    @Override public double area() { return Math.PI * r * r; }
}

public class ShapesDemo {
    public static void main(String[] args) {
        Shape[] shapes = {new Rectangle(4, 2.5), new Circle(1)};
        for (Shape s : shapes) {
            System.out.println(s.describe());
        }
    }
}
Output
Area: 10.00
Area: 3.14

Key takeaway#

Finished this lesson?