iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Interfaces

A Java interface is a contract: methods other classes must implement. Since Java 8, interfaces can also have default methods and static methods. Functional interfaces drive lambdas.

Define, implement, default, sealed

EXAMPLE
// 1) Basic interface
public interface Animal {
    String name();
    String sound();
}

// 2) Implement
public record Dog(String name) implements Animal {
    @@Override public String sound() { return "woof"; }
}

public class Cat implements Animal {
    private final String name;
    public Cat(String name) { this.name = name; }
    @@Override public String name()  { return name; }
    @@Override public String sound() { return "meow"; }
}

// 3) Polymorphism — accept any Animal
void describe(Animal a) {
    System.out.println(a.name() + " says " + a.sound());
}

describe(new Dog("Rex"));
describe(new Cat("Whiskers"));

// 4) Multiple interfaces
public interface Swims    { default void swim()  { System.out.println("swimming"); } }
public interface Flies    { default void fly()   { System.out.println("flying"); } }

public class Duck implements Animal, Swims, Flies {
    public String name()  { return "Donald"; }
    public String sound() { return "quack"; }
}

new Duck().swim();
new Duck().fly();

// 5) default methods — backward-compatible additions
public interface Collection<T> {
    void add(T item);
    int size();

    default boolean isEmpty() { return size() == 0; }    // free for implementors
}

// 6) static methods on interfaces — factory + helpers
public interface Person {
    String name();

    static Person of(String name) {
        return () -> name;       // lambda implementing single-method interface
    }
}

Person ada = Person.of("Ada");
System.out.println(ada.name());

// 7) Functional interfaces — exactly ONE abstract method
@@FunctionalInterface
public interface Greeting {
    String greet(String name);
}

Greeting hello = (name) -> "Hello, " + name;
System.out.println(hello.greet("World"));

// Standard library functional interfaces — java.util.function
//   Function<T, R>     : R apply(T)
//   BiFunction<T,U,R>  : R apply(T, U)
//   Predicate<T>       : boolean test(T)
//   Consumer<T>        : void accept(T)
//   Supplier<T>        : T get()
//   UnaryOperator<T>   : T apply(T)
//   BinaryOperator<T>  : T apply(T, T)

// 8) Interface inheritance
public interface Named {
    String name();
}

public interface Animal2 extends Named {
    String sound();
}

public class Robot implements Animal2 {
    public String name()  { return "R2D2"; }
    public String sound() { return "beep"; }
}

// 9) private interface methods (Java 9+) — share helper logic between default methods
public interface Logger {
    default void info(String msg) { print("INFO",  msg); }
    default void warn(String msg) { print("WARN",  msg); }
    default void error(String msg) { print("ERROR", msg); }

    private void print(String level, String msg) {
        System.out.println("[" + level + "] " + msg);
    }
}

// 10) sealed interfaces (Java 17+) — restrict who can implement
public sealed interface Shape permits Circle, Rectangle, Triangle { }

public record Circle(double radius)         implements Shape { }
public record Rectangle(double w, double h) implements Shape { }
public record Triangle(double base, double height) implements Shape { }

// Now switch is EXHAUSTIVE
static double area(Shape s) {
    return switch (s) {
        case Circle c             -> Math.PI * c.radius() * c.radius();
        case Rectangle r          -> r.w() * r.h();
        case Triangle t           -> 0.5 * t.base() * t.height();
    };
}

// 11) Marker interfaces — empty interfaces signalling capability
public interface Serializable { }
public interface Cloneable    { }

// Use sparingly — annotations + sealed types are usually clearer.

// 12) Static nested + inner classes — implement interfaces from within
public class Parent {
    private static class Helper implements Comparable<Helper> {
        @@Override public int compareTo(Helper other) { return 0; }
    }
}

// 13) Anonymous inner class — quick implementation
Comparator<String> byLength = new Comparator<String>() {
    @@Override public int compare(String a, String b) { return a.length() - b.length(); }
};
// Or use a lambda (preferred for functional interfaces):
Comparator<String> byLengthLambda = (a, b) -> a.length() - b.length();

// 14) Method reference
List<String> names = List.of("Cy", "Ada", "Bo");
names.sort(Comparator.comparingInt(String::length));

// 15) Interface vs abstract class
// interface: pure contract, multiple inheritance, no state
// abstract class: contract + shared implementation + state, single inheritance
//
// Default to interface; use abstract class when:
//   - There's shared state between subclasses
//   - You want to enforce a single inheritance chain
//   - You provide complex shared methods that can't fit as `default`

// 16) Generic interface
public interface Repository<T, ID> {
    Optional<T> findById(ID id);
    List<T>     findAll();
    T           save(T entity);
    void        delete(ID id);
}

public class UserRepository implements Repository<User, Long> {
    @@Override public Optional<User> findById(Long id) { /* ... */ }
    @@Override public List<User> findAll()             { /* ... */ }
    @@Override public User save(User u)                { /* ... */ }
    @@Override public void delete(Long id)             { /* ... */ }
}

// 17) Common patterns

// Strategy via interface
public interface SortStrategy<T> {
    List<T> sort(List<T> input);
}

// Observer
public interface EventListener<E> {
    void onEvent(E event);
}

// Builder fluent interface
public interface CarBuilder {
    CarBuilder withColor(String c);
    CarBuilder withEngine(Engine e);
    Car build();
}

// 18) Common bugs
//   • Forgetting @@Override → compiler doesn't catch typo'd signatures
//   • Default methods conflict from multiple interfaces — must override explicitly
//   • Sealing without `permits` clause → compile error
//   • Functional interface with > 1 abstract method → no longer lambda-compatible
//   • Comparing interfaces with == — usually wrong; use Objects.equals

// 19) Modern Java idioms
//   • Records implement interfaces concisely (one method per `default` you skip)
//   • Sealed interfaces + pattern matching = exhaustive ADTs
//   • Use `@@FunctionalInterface` annotation to assert single-abstract-method
//   • Prefer `default` methods over abstract base classes when adding behaviour
//   • Java 21+ : pattern matching for switch with sealed → no `default:` arm needed

Why it matters

Interfaces (especially with default and sealed) cover most needs for inheritance + polymorphism. Reach for an abstract class only when you need shared mutable state — otherwise the interface stays cleaner.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
interface Greet {
    String hello();
    default String wave() { return "👋"; }
}
class User implements Greet {
    public String hello() { return "hi"; }
}
Try it Yourself »

Exercise

Adopt an interface.

class User Greet { public String hello() { return "hi"; } }

Discussion

Loading…