JAVA / ABSTRACT CLASSES AND INTERFACES
Abstract methods and the template method shape
Build an algorithm inside an abstract base class whose varying steps are abstract methods, and judge when that fixed skeleton helps or gets in the way.
What you will learn
- Write the invariant algorithm once in a concrete base method that calls abstract steps.
- Mark the template method final so subclasses cannot reorder or skip its steps.
- Separate must-implement abstract steps from optional hooks that ship a default body.
- Never call an abstract step from a constructor; subclass fields are still null or 0.
Understanding Abstract methods and the template method shape
An abstract method is a call the base class is allowed to make but cannot answer. That sounds useless until you notice it lets you finish an algorithm before you know all of its parts: render() can append a title it has no idea how to produce, because the call to title() is resolved at runtime against the object's actual class. Control flows downward here, from base into subclass, which is the reverse of the usual super.doSomething() direction: the subclass supplies answers while the base class keeps every decision about order and structure.
That inversion has a standard shape. One concrete method holds the sequence, the shared setup, the loop and the error handling, and each varying piece becomes its own small method. Those pieces come in two flavours: abstract steps a subclass must implement, which the compiler enforces, and hooks, ordinary methods with a reasonable default that a subclass may replace. Declaring the orchestrating method final and the steps protected is what makes the design real, since final turns "the steps always run in this order" from a comment into a guarantee, and protected stops callers from invoking one step out of sequence.
The useful mental model is to read the abstract steps as parameters of the algorithm that happen to be supplied by inheritance rather than by argument. This pays off when the sequence is the valuable, easy-to-get-wrong part and the steps are genuinely small, such as always closing a resource or always emitting a header. It stops paying off when subclasses start wanting a different order, want to skip steps, or need to mix two variations, because a class can extend only one skeleton; at that point passing the varying behaviour in as a value is the better trade.
import java.util.List;
abstract class Report {
// The skeleton: header, indented rows, footer. The order never varies.
public final String render() {
StringBuilder out = new StringBuilder();
out.append("== ").append(title()).append(" ==\n");
for (String row : rows()) {
out.append(" ").append(row).append('\n');
}
out.append(footer()).append('\n');
return out.toString();
}
// Steps every subclass must supply.
protected abstract String title();
protected abstract List<String> rows();
// Hook: overriding this one is optional.
protected String footer() {
return "-- end --";
}
}
class SalesReport extends Report {
@Override protected String title() { return "Sales"; }
@Override protected List<String> rows() { return List.of("north 120", "south 95"); }
}
class AuditReport extends Report {
@Override protected String title() { return "Audit"; }
@Override protected List<String> rows() { return List.of("login ok"); }
@Override protected String footer() { return "-- signed --"; }
}
public class Main {
public static void main(String[] args) {
Report[] reports = { new SalesReport(), new AuditReport() };
for (Report report : reports) {
System.out.print(report.render());
}
}
}A concrete method in an abstract class can express a complete algorithm in terms of calls it cannot implement, because each of those calls is dispatched at runtime to the subclass that is actually running.
Worked examples
The skeleton enforces cleanup
The base class wraps the abstract step in try/finally so no subclass can forget to close what it opened.
abstract class Job {
public final void run() {
System.out.println("open " + name());
try {
work();
} catch (RuntimeException e) {
System.out.println("failed: " + e.getMessage());
} finally {
System.out.println("close " + name());
}
}
protected abstract String name();
protected abstract void work();
}
class GoodJob extends Job {
@Override protected String name() { return "good"; }
@Override protected void work() { System.out.println("working"); }
}
class BadJob extends Job {
@Override protected String name() { return "bad"; }
@Override protected void work() { throw new IllegalStateException("disk full"); }
}
public class Main {
public static void main(String[] args) {
new GoodJob().run();
new BadJob().run();
}
}Example explained
Line 1work() is abstract, so run() compiles against a call it cannot resolve until an object exists.
Line 2BadJob.work() throws, yet "close bad" still prints, because the try/finally lives in the base class instead of in every subclass.
Line 3BadJob contains no error handling at all; the subclass only decides what the work is, never how failures are handled.
Line 4run() is final, so a subclass cannot replace it with a version that quietly drops the finally block.
Why the template must not run in a constructor
An abstract step called from the superclass constructor sees the subclass fields before their initializers have run.
abstract class Validator {
private final String banner;
Validator() {
banner = "checked: " + describe();
}
protected abstract String describe();
String banner() {
return banner;
}
}
class RangeValidator extends Validator {
private int max = 10;
private String label = "range";
@Override
protected String describe() {
return label + " up to " + max;
}
}
public class Main {
public static void main(String[] args) {
RangeValidator v = new RangeValidator();
System.out.println(v.banner());
System.out.println("later: " + v.describe());
}
}Example explained
Line 1The Validator constructor body runs before RangeValidator's field initializers, so label is still null and max is still 0.
Line 2describe() nonetheless dispatches to the subclass override, because dynamic dispatch is already active inside a superclass constructor.
Line 3The second call returns "range up to 10", which proves the fields are correct and only the timing was wrong.
Line 4Fix it by keeping the template in a normal method that the caller invokes on a fully constructed object.
Important notes
final on the template method does not make the class final: subclassing is still allowed, only rewriting the skeleton is blocked.
A hook is only worth having if some subclasses keep the default; if every subclass overrides it, make it abstract so the compiler demands an implementation.
Common mistakes
Leaving the template method non-final, then overriding it in one subclass to tweak the order: that subclass silently loses the shared setup or the finally block, and nothing fails at compile time.
Calling an abstract step from the abstract class's constructor, so the override runs against uninitialized subclass state and you get NullPointerException or 0 in a place that looks impossible.
Declaring a step public in the base class and then trying to narrow the override to protected or private, which fails with "attempting to assign weaker access privileges"; decide on protected in the base class first.
Try it yourself
Change, predict, then run
Write an abstract class Box with a final show() that prints a line of five border characters, then the text returned by abstract body(), then the border line again, where the border character comes from a hook borderChar() returning '-'. Add two subclasses, one that keeps the default border and one that overrides borderChar() to '*', and call show() on both.
Open the Java workspaceCheck your understanding
An abstract class Job has public final void run() that calls setUp(), then the abstract work(), with tearDown() in a finally block. A new subclass genuinely must not run setUp(). What is the right move?
- Turn setUp() into a hook the new subclass overrides with an empty body, leaving run() and its finally block untouched.
- Drop final from run() and override the whole method in that subclass, omitting the setUp() call.
- Make setUp() abstract so every subclass writes its own version, and give the new one an empty body.
- Have the new subclass call work() directly from client code and skip run() for that case.
Show answer
A hook lets one step vary while the guarantee the template exists for stays intact: work() always runs inside try with tearDown() in finally. Overriding run() looks like less code, but a hand-written copy can quietly lose the finally block and the compiler will not notice; making setUp() abstract forces every existing subclass to re-declare setup code that already worked.