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"; } }
Ten letters.
Discussion
Loading…