Interfaces in Java β€” Contracts, Default and Static Methods

An interface is a pure contract β€” a set of method signatures any implementing class must provide. Interfaces are Java's answer to multiple inheritance: a class can implement any number of them.

Declaration

public interface Shape {
    double area();                          // implicitly public abstract
    double perimeter();
}

public class Circle implements Shape {
    private final double r;
    public Circle(double r) { this.r = r; }

    @Override public double area()      { return Math.PI * r * r; }
    @Override public double perimeter() { return 2 * Math.PI * r; }
}

Default methods (Java 8+)

Allow interfaces to evolve without breaking existing implementations:

public interface Shape {
    double area();
    default boolean isLarge() { return area() > 100; }   // concrete
}

Static methods (Java 8+)

public interface Shape {
    double area();
    static Shape unitCircle() {
        return () -> Math.PI;
    }
}

var s = Shape.unitCircle();

Private methods (Java 9+)

public interface Logger {
    default void info(String msg)  { log("INFO",  msg); }
    default void error(String msg) { log("ERROR", msg); }
    private void log(String level, String msg) {         // shared helper, not exposed
        System.out.println("[" + level + "] " + msg);
    }
}

Constants

Every field in an interface is implicitly public static final β€” i.e. a constant. Declaring constants in an interface is legal but usually an anti-pattern (the implementer "inherits" a namespace pollution). Put constants in a dedicated class.

Functional interfaces

@FunctionalInterface
public interface Mapper<T, R> {
    R apply(T input);
}

Mapper<String, Integer> len = s -> s.length();
int n = len.apply("hello");                 // 5

A functional interface has exactly one abstract method. Lambdas and method references are their instances.

Marker interfaces

public interface Serializable {}            // empty β€” just a "tag"
public interface Cloneable {}
public interface RandomAccess {}             // hint that List.get is O(1)

Marker interfaces are a legacy pattern. Modern code uses annotations for the same purpose (@Documented, @Retention, …).

Common mistakes

  • Adding a default method in a popular interface β€” can conflict with existing implementations that already define the same name in a parent class.
  • Using interfaces to hold constants β€” constants pollute implementers' namespace. Use a class with a private constructor.
  • Interface with one implementation and no chance of a second β€” premature abstraction. Add the interface when it's genuinely needed.

Related

Pillar: OOP. Siblings: abstraction, sealed classes, @FunctionalInterface.