PrepZone Logo
PrepZone

Interfaces and Abstract Classes

How to choose between them, how default methods changed the answer, and what annotations add.

Why this matters

  • This is the most frequently asked design question in Java interviews, and default methods in Java 8 changed the correct answer.
  • Choosing wrongly shows up later as a class that cannot extend the thing it needs to, because its one extends slot is already taken.
  • Every modern Java API — streams, collections, Spring, JDBC — is defined in interfaces, so reading those APIs requires fluency here.

Interfaces

An interface is a contract. Before Java 8 it could hold nothing but abstract method signatures and constants; three releases have since widened it.

Java
public interface Repository<T, ID> {

    // Abstract: implicitly public abstract
    Optional<T> findById(ID id);
    List<T> findAll();
    void save(T entity);

    // Default: a body, inheritable and overridable — added in Java 8
    default boolean existsById(ID id) {
        return findById(id).isPresent();
    }

    // Static: a utility bound to the interface, not inherited — added in Java 8
    static <T, ID> Repository<T, ID> empty() {
        return new EmptyRepository<>();
    }

    // Private: shared helper for default methods, hidden from implementers — added in Java 9
    private void auditLookup(ID id) { }

    // Constant: implicitly public static final
    int MAX_BATCH_SIZE = 500;
}

Member rules, stated once

  • Methods without a body are implicitly public abstract.
  • Fields are implicitly public static final — constants, never instance state.
  • default methods carry a body that implementers inherit and may override.
  • static methods belong to the interface itself and are not inherited by implementers.
  • private methods exist only to share code between default methods in the same interface.
  • An interface has no constructor and can never be instantiated.

Abstract classes

An abstract class cannot be instantiated and may mix finished and unfinished methods. Crucially, it can hold state and a constructor.

Java
public abstract class Shape {
    private final String name;          // real instance state

    protected Shape(String name) {      // constructor, called via super()
        this.name = name;
    }

    public abstract double area();       // subclasses must supply this

    public String describe() {          // shared, concrete behaviour
        return name + " with area " + String.format("%.2f", area());
    }
}

public class Circle extends Shape {
    private final double radius;

    public Circle(double radius) {
        super("Circle");
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

Note what describe() does: it calls the abstract area(). The parent defines the algorithm and the child supplies one step. That is the template method pattern, and it is the clearest reason to prefer an abstract class.

Abstract class
Shared fieldsCan hold state
ConstructorsRuns setup for subclasses
One parent onlySingle inheritance
Interface
Constants onlyNo instance state
default / static methodsShared helpers since Java 8
Many at onceMultiple inheritance of type
Reach for an abstract class when subclasses share state and partial behaviour. Reach for an interface when unrelated types need to agree on a capability.

Choosing between them

AspectInterfaceAbstract class
How many per classAny numberExactly one
Instance fieldsNo — constants onlyYes
ConstructorNoYes
Method bodiesdefault, static, privateAny concrete method
Access modifiers on memberspublic, or private for helpersAny, including protected
ExpressesA capability — can-doAn identity — is-a
Adding a method laterSafe if you give it a defaultSafe if you give it a body
  • How many per class

    InterfaceAny number
    Abstract classExactly one
  • Instance fields

    InterfaceNo — constants only
    Abstract classYes
  • Constructor

    InterfaceNo
    Abstract classYes
  • Method bodies

    Interfacedefault, static, private
    Abstract classAny concrete method
  • Access modifiers on members

    Interfacepublic, or private for helpers
    Abstract classAny, including protected
  • Expresses

    InterfaceA capability — can-do
    Abstract classAn identity — is-a
  • Adding a method later

    InterfaceSafe if you give it a default
    Abstract classSafe if you give it a body

The single-inheritance limit is usually the deciding factor.

A practical rule

  • Need to declare a capability that unrelated types might have? Interface. Comparable, Runnable, AutoCloseable are all capabilities.
  • Need shared state or a constructor? Abstract class, because an interface cannot hold either.
  • Want to define an algorithm with holes for subclasses to fill? Abstract class, using the template method shape above.
  • Unsure? Start with an interface. It leaves the subclass's one extends slot free, and you can add an abstract base class later if duplication appears.

Both together is a common and good pattern: a public interface for callers, plus an abstract skeleton implementation for implementers. The JDK does this throughout — List with AbstractList, Map with AbstractMap.

When two interfaces collide

Default methods reintroduced a limited diamond, and the language forces you to resolve it.

Java
interface Flyer {
    default String move() { return "flying"; }
}

interface Swimmer {
    default String move() { return "swimming"; }
}

class Duck implements Flyer, Swimmer {
    @Override
    public String move() {
        return Flyer.super.move() + " and " + Swimmer.super.move();
    }
}

Without the override this would not compile. The Interface.super.method() syntax is the only way to name a specific inherited default.

Functional interfaces

An interface with exactly one abstract method can be implemented by a lambda. default and static methods do not count towards the one.

Java
@FunctionalInterface
public interface Validator<T> {
    boolean test(T value);                 // the single abstract method

    default Validator<T> and(Validator<T> other) {
        return value -> test(value) && other.test(value);
    }
}
Java
Validator<String> notBlank = text -> !text.isBlank();
Validator<String> shortEnough = text -> text.length() <= 50;

Validator<String> rule = notBlank.and(shortEnough);
System.out.println(rule.test("Core Java"));    // true

The @FunctionalInterface annotation is optional but worth adding: it makes the compiler reject a second abstract method, protecting every lambda that depends on the shape.

Marker interfaces and annotations

A marker interface has no members and exists purely to tag a type — Serializable and Cloneable are the well-known examples. Code then tests the tag with instanceof.

Annotations largely replaced them, because an annotation can carry parameters and apply to methods and fields as well as types.

Java
@Deprecated(since = "3.2", forRemoval = true)
public void oldApi() { }

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

Built-in annotations worth knowing

  • @Override — compile-time check that you really are overriding.
  • @Deprecated — warns callers, and forRemoval = true says the removal is planned.
  • @FunctionalInterface — enforces exactly one abstract method.
  • @SuppressWarnings — silences a specific warning in the narrowest possible scope.
  • @SafeVarargs — asserts a generic varargs method does not leak an unsafe array.

Common misreadings

  • "Interfaces cannot have method bodies." They can, since Java 8: default and static, plus private helpers since Java 9.
  • "Abstract classes must contain abstract methods." They need not. Declaring a class abstract only prevents instantiation.
  • "Interface fields are instance state." They are public static final constants, shared and unmodifiable.
  • "An abstract class cannot have a constructor." It must, and subclasses call it through super().
  • "Interfaces are always the right default." When you need shared state, only an abstract class works.
  • "default methods brought multiple inheritance of state." They brought behaviour only. Instance fields are still single-inheritance.

Quick recall

Everything you need if you only revisit this box.

  • Interface = capability; abstract class = identity with shared implementation.
  • An interface holds abstract, default, static and private methods plus public static final constants — never instance state, never a constructor.
  • An abstract class can hold state, a constructor and protected members, and is the right tool for a template method.
  • A class implements many interfaces but extends one class. That limit usually decides the choice.
  • Conflicting defaults must be resolved by overriding, using Interface.super.method(). A concrete class method always beats a default.
  • One abstract method makes an interface functional and lambda-compatible; @FunctionalInterface enforces it.
  • Annotations have largely replaced marker interfaces because they carry parameters and wider targets.

Test yourself

Answer these before moving on — recall is what makes it stick.