Skip to main content

Week 3 Day 2 — Interfaces vs Abstract Classes

Goal

Today I want a decision rule: interface for a capability, abstract class for a shared implementation skeleton.

Main questions:

  1. What can an interface hold?
  2. What can an abstract class hold?
  3. When do I pick which?
  4. What are default methods for?
  5. Why does Spring Data use interfaces for repositories?

1. Interface

An interface is a type that describes what an object can do.

public interface OrderRepository {
Order save(Order order);
Optional<Order> findById(long id);
}

A class can implement many interfaces. That is Java’s way to have multiple types without multiple class inheritance.

Modern interfaces may contain:

  • abstract methods (the usual)
  • default methods with a body
  • static methods
  • private methods (helpers for defaults)
  • constants (public static final fields)

They cannot hold instance fields. They cannot remember per-object state unless the implementing class does.

Memory sentence:

An interface is a capability. It does not store instance state.


2. Abstract class

An abstract class is a partial class. It can have fields, constructors, implemented methods, and abstract methods.

public abstract class Payment {
private final Money amount;

protected Payment(Money amount) {
this.amount = amount;
}

public final Money amount() {
return amount;
}

public abstract void settle();
}

A class can extend only one abstract class. I use that when implementations truly share state and a template.

abstract on a class means I cannot new it. abstract on a method means subclasses must implement it (unless they are also abstract).


3. Decision rule

I needI use
A seam for tests and extra implementationsinterface
Several independent capabilities (Closeable, Comparable)interface
Shared fields and a constructor invariantabstract class
A template method that calls abstract stepsabstract class
JDK proxy / Spring Data repositoryinterface

If I am unsure, I start with an interface. I extract an abstract class later only when two implementations copy real state or a real algorithm.

A class can do both: class StripePayment extends Payment implements Refundable.


4. Default methods

default methods let me add a method to an interface without breaking existing implementors.

public interface OrderRepository {
Order save(Order order);

default Order saveAndFlush(Order order) {
return save(order);
}
}

If two interfaces provide the same default method, the class must override and pick (or call InterfaceName.super.method()).

Default methods are not a place to hide business services. They have no instance fields. Heavy defaults become an abstract class or a collaborator.

Functional interfaces (one abstract method) are the target types for lambdas: Function, Predicate, Converter. Spring uses them all over (Converter<S,T>, ApplicationListener).


5. Spring connection

Repositories:

public interface OrderRepository extends JpaRepository<Order, Long> {}

I write an interface. Spring Data creates a proxy that implements it. My service depends on the interface. That is polymorphism plus a generated implementation.

Services: I often still write a concrete class without an interface when there is one implementation and I test with the constructor. I add an interface when I have two implementations, a module boundary, or I want a JDK proxy.

JDK vs CGLIB proxies:

  • JDK proxy: requires an interface. The proxy implements it, does not extend the class.
  • CGLIB: subclasses the concrete class. Needs a non-final class and a non-private constructor.

If the injection type is the class, I get a CGLIB subclass (in typical Spring setup). If the injection type is the interface, I can get a JDK proxy. Self-invocation problems exist in both cases because this is not the proxy.


6. Common traps

Trap 1: Abstract class “just in case we reuse fields.”
Empty abstract classes are a tax. Wait for real sharing.

Trap 2: Fat interface (OrderRepository that also sends email).
Implementors fake half the methods. Split types (Interface Segregation, tomorrow).

Trap 3: Default method that needs state.
Interfaces cannot hold it. The default will cheat with statics or throw.

Trap 4: “Spring requires an interface for every service.”
It does not. Interfaces are for substitution and some proxy modes. One concrete @Service is fine.


Practice Questions and Answers

Question 1

Interface vs abstract class — when each?

Answer:

Interface for a capability and a test seam, especially when a class needs several types. Abstract class when implementations share fields, a constructor, or a template algorithm. I start with an interface unless I already have shared state.


Question 2

Why can a class implement many interfaces but extend only one class?

Answer:

Java has single class inheritance to avoid the diamond problem on state. Interfaces add types without instance fields, so multiple interfaces are allowed. Default methods can still conflict; the class must resolve that.


Question 3

What is a default method for?

Answer:

To evolve an interface with a method that has a reasonable implementation, without breaking existing implementors. Not for storing state or growing a hidden service.


Question 4

Why are Spring Data repositories interfaces?

Answer:

The application describes the capability (save, findById, query methods). Spring Data supplies a proxy implementation at runtime. My services depend on the interface, so tests can pass a fake.


Question 5

Do I need an interface for every Spring @Service?

Answer:

No. I add one when I need a second implementation, a module API, or a JDK proxy. A single concrete service with constructor injection is a valid design.


Memory sentences

Interface = capability. Abstract class = shared skeleton with state.

Start with an interface. Extract an abstract class when state is really shared.

Spring Data repositories are interfaces so the container can supply the implementation.

Next: Week 3 Day 3 — Composition vs Inheritance