Abstraction in Java β€” Abstract Classes and Interfaces

Abstraction is the practice of exposing what something does while hiding how. In Java it takes two forms: abstract classes (partial implementation) and interfaces (pure contract, plus default methods since Java 8).

Abstract class

public abstract class Shape {
    private final String id = UUID.randomUUID().toString();   // shared state
    public String id() { return id; }                          // shared behaviour
    public abstract double area();                              // each subclass must provide
}

public class Circle extends Shape {
    private final double r;
    public Circle(double r) { this.r = r; }
    @Override public double area() { return Math.PI * r * r; }
}
  • Can have fields (state).
  • Can have constructors.
  • Subclasses extend with extends β€” only one parent.

Interface

public interface Shape {
    double area();                                // abstract (implicit)

    default boolean isLarge() {                    // default method β€” Java 8+
        return area() > 100;
    }

    static Shape zero() {                          // static β€” Java 8+
        return () -> 0.0;
    }
}

public class Square implements Shape {
    private final double s;
    public Square(double s) { this.s = s; }
    public double area() { return s * s; }
}
  • Only public static final fields (constants).
  • No constructor.
  • A class implements any number with implements.

When to use which

Use abstract class whenUse interface when
You want to share state and partial implementation.You want a pure contract.
Subclasses are clearly related (is-a Shape).Implementers are diverse (Shape, Animal, Serialisable).
You need constructors or non-public methods.You need multiple inheritance.

Sealed types (Java 17+)

public sealed interface Result<T> permits Ok, Err {}
public record Ok<T>(T value)   implements Result<T> {}
public record Err<T>(String e) implements Result<T> {}

String msg = switch (result) {
    case Ok<?>  o -> "ok " + o.value();
    case Err<?> e -> "err " + e.e();
    // no default β€” compiler knows the hierarchy is closed
};

Common mistakes

  • Abstract class with a single concrete subclass β€” often a sign that you should just use a normal class.
  • Interface with a single implementation and no other use β€” premature abstraction. Add the interface when you actually need a second implementation.
  • Protected fields in an abstract class β€” leaks internals to subclasses. Expose via protected methods.

Related

Pillar: OOP in Java. Siblings: interfaces, sealed classes, abstract keyword.