Java Abstraction
›Short versionOpen if you only have a minute.
- Abstraction is a small promise. Callers depend on the promise. A concrete class carries out the mechanism.
- An abstract class cannot be constructed. It may have fields, constructors, concrete methods, and abstract methods.
- An abstract method has a signature and no body. The first concrete subclass must implement it.
- A template method is a concrete method that runs the steps in order and calls abstract or protected hooks for the parts that vary.
- A class extends one class. It can implement many interfaces. Shared instance state points toward an abstract class.
- An interface can have default methods and still has no per-instance fields. Its fields are public, static, and final.
Checkout asks for a payment to be captured. It should not also know how a card network frames a message, or how a bank transfer quotes a reference. is that split. The caller keeps the verb. A keeps the protocol.
A class that is only half written
An is the right half to share when every payment has an amount check and a place to record the result, and the vendor call is the part that changes. The is the unfinished step.
public abstract class Payment {
private final String id;
protected Payment(String id) {
if (id == null || id.isBlank()) {
throw new IllegalArgumentException("id");
}
this.id = id;
}
public final String settle(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("amount");
}
String reference = capture(amount);
return id + ":" + reference;
}
protected abstract String capture(int amount);
}settle is a . It is final, so a subclass cannot skip the amount check and still look like a payment. The subclass writes capture only.
public class CardPayment extends Payment {
public CardPayment(String id) {
super(id);
}
@Override
protected String capture(int amount) {
return "card-" + amount;
}
}new Payment("p1") does not compile. Payment payment = new CardPayment("p1") does. The variable type is the promise. The constructor names the mechanism. Protected capture stays off the public API, so callers cannot invoke the step out of order.
Abstract class or interface
An is the better promise when the types do not share fields and do not form one family. A receipt, a payment, and a webhook can all be Retryable without extending a common base. A class implements any number of interfaces and extends one class, so the interface does not spend the single extends slot.
public interface Retryable {
boolean retry();
}
public final class CardPayment extends Payment implements Retryable {
@Override
public boolean retry() {
return true;
}
}Use the abstract class when subclasses share state or a protected helper, and when a template should own the order of steps. Use the interface when unrelated classes should be callable the same way, and when each implementation already has a superclass. Default methods let an interface ship a fallback body, but they still cannot store a per-object id. The moment you need that id on every implementation, you are describing an abstract class, or a small composed collaborator each implementation holds.
Making a class abstract only to stop new is a blunt tool. A private constructor does that for a utility class that will never have subclasses. An abstract class that exists so subclasses can be plugged into a template is the case this page is about.
$ javac Payment.java CardPayment.java$ java Checkout
Card payments and bank payments both need a private id and the same amount check, and only the vendor call differs. Interface or abstract class?
An abstract class. The id is instance state, and the amount check is a concrete template method you can mark final. capture stays abstract. An interface cannot hold that id per object. You can still add an interface later if a different family of types must be retried alongside payments.