Interfaces & Abstract Classes

Abstract Classes Revisited

15 min Lesson 1 of 14

Abstract Classes Revisited

You already know how to extend a class and override methods. In this lesson we step back and look at abstract classes with fresh eyes — not as a syntax curiosity, but as a design tool. By the end you will know when an abstract class is the right choice, how to mix abstract and concrete methods effectively, and what pitfalls to avoid.

What the abstract Keyword Actually Means

Placing abstract on a class does two things simultaneously:

  • It forbids direct instantiation — no one can call new Shape() if Shape is abstract.
  • It allows abstract methods — method declarations with no body that subclasses must implement.

These two rules work together: you cannot create an instance of a class that has unimplemented methods, so the compiler enforces the contract.

// Abstract class — cannot be instantiated directly public abstract class Shape { // Abstract method — no body, no braces public abstract double area(); // Concrete method — has a body, inherited as-is public String describe() { return "I am a shape with area " + area(); } } public class Circle extends Shape { private final double radius; public Circle(double radius) { this.radius = radius; } // Must implement the abstract contract @Override public double area() { return Math.PI * radius * radius; } }

Notice that describe() is fully implemented in the abstract class and calls area(). At runtime the call is dispatched to Circle.area(). This is the Template Method pattern in its simplest form: the base class defines the algorithm skeleton; subclasses fill in the blanks.

Abstract vs Concrete Methods — Choosing What to Provide

Every method in an abstract class is either abstract (deferred) or concrete (shared). The rule of thumb:

  • Make a method abstract when each subclass genuinely needs a different implementation and there is no sensible default.
  • Make a method concrete when most or all subclasses would do the same thing, or when you want to build reusable logic that calls the abstract parts.
public abstract class Report { // Template method — orchestrates the workflow public final void generate() { fetchData(); String body = format(); // abstract — varies per report type deliver(body); } protected abstract String format(); // Each report type defines its own layout private void fetchData() { System.out.println("Fetching data from database..."); } private void deliver(String body) { System.out.println("Sending report:\n" + body); } } public class PdfReport extends Report { @Override protected String format() { return "[PDF] Sales summary: Q2 revenue up 12%"; } } public class CsvReport extends Report { @Override protected String format() { return "month,revenue\nJune,1200000"; } }
Note on final + template methods: Marking the orchestrating method final prevents subclasses from accidentally breaking the workflow. The abstract hook methods are what subclasses customize — the skeleton stays fixed.

Partial Implementations and Constructor Injection

Abstract classes can have constructors, fields, and any amount of concrete code. This is a key advantage over interfaces (which we cover next lesson): you can store state and reuse initialisation logic.

public abstract class Animal { private final String name; // Subclasses call super() to provide the name protected Animal(String name) { this.name = name; } public String getName() { return name; } // Abstract — each animal makes its own sound public abstract String sound(); // Concrete — same greeting logic for everyone public void greet() { System.out.println(getName() + " says: " + sound()); } } public class Dog extends Animal { public Dog(String name) { super(name); } @Override public String sound() { return "Woof"; } } public class Cat extends Animal { public Cat(String name) { super(name); } @Override public String sound() { return "Meow"; } } // Usage Animal dog = new Dog("Rex"); dog.greet(); // Rex says: Woof
Use protected for abstract-class constructors when the class should only be constructed through subclasses. public works too (the compiler still blocks direct instantiation), but protected signals intent more clearly.

When an Abstract Class is the Right Tool

Ask yourself these questions:

  1. Is there a true "is-a" relationship? A Dog is an Animal; an abstract class models that parent type.
  2. Do subclasses share real state? If every subclass needs a name field, put it in the abstract class.
  3. Is there shared algorithm structure? Template methods shine when the steps are the same but some steps vary.
  4. Is single inheritance enough? A class can only extend one parent. If you need multiple types to be composed, you will need interfaces (next lesson).
Do not use an abstract class just to prevent instantiation. If you have no shared state and no concrete methods, an interface is almost certainly the better choice. Abstract classes with nothing but abstract methods waste a precious inheritance slot.

Compiler Guarantees

The Java compiler enforces the abstract contract at every step:

  • Trying to instantiate an abstract class is a compile error, not a runtime error.
  • A subclass that does not implement all abstract methods must itself be declared abstract.
  • You can hold a reference of type Shape or Animal — polymorphism works normally; only instantiation is forbidden.
Shape s = new Shape(); // Compile error: Shape is abstract Shape c = new Circle(5); // OK — Circle is concrete System.out.println(c.describe()); // calls Circle.area() at runtime

Summary

An abstract class is a partially implemented type that forces subclasses to complete the contract while sharing reusable code. Use it when you have a true hierarchy with shared state or behaviour, and when the template-method pattern naturally emerges. In the next lesson we introduce interfaces — a complementary (and often preferred) mechanism for expressing contracts without inheritance.