OOP

21. Abstract Classes

An abstract class cannot be instantiated and may contain abstract methods (no body) that subclasses must implement, alongside concrete methods, fields, and constructors โ€” use it when you want to share state or a partial implementation.

Java 21 (core language)

Explanation

Abstract classes sit between concrete classes (full implementation) and interfaces (pure contract). The key advantages: they can hold state (instance fields), have constructors (called via super() in subclasses), and provide concrete methods that subclasses inherit without change.

Use an abstract class when: multiple classes share meaningful common state or implementation (a Template Method pattern), you want to provide a protected internal API, or you need constructor logic to run before the subclass completes initialisation.

Prefer interfaces over abstract classes in most cases โ€” especially since Java 8 added default methods to interfaces. The interface approach allows a class to satisfy multiple contracts (implements A, B), while abstract class inheritance is limited to one (extends). The classic rule: choose interface for the "can-do" capability, abstract class for the "is-a" shared skeleton.

Example

public class AbstractClassDemo {
    abstract static class Report {
        private final String title; // state shared by all reports
        private final String author;

        Report(String title, String author) {
            this.title  = title;
            this.author = author;
        }

        // Template Method pattern: concrete structure, abstract steps
        public final void generate() {
            System.out.println("=== " + title + " by " + author + " ===");
            printBody();        // abstract โ€” subclass fills in
            printFooter();      // concrete default
        }

        protected abstract void printBody();

        protected void printFooter() {
            System.out.println("--- End of report ---");
        }

        public String getTitle() { return title; }
    }

    static class SalesReport extends Report {
        private final double total;

        SalesReport(String author, double total) {
            super("Sales Report", author);
            this.total = total;
        }

        @Override
        protected void printBody() {
            System.out.printf("Total sales: $%.2f%n", total);
        }
    }

    static class AuditReport extends Report {
        AuditReport(String author) { super("Audit Report", author); }

        @Override
        protected void printBody() {
            System.out.println("All systems nominal.");
        }

        @Override
        protected void printFooter() {
            System.out.println("Verified by compliance team.");
        }
    }

    public static void main(String[] args) {
        Report[] reports = {
            new SalesReport("Alice", 12_500.75),
            new AuditReport("Bob")
        };

        for (Report r : reports) {
            r.generate();
            System.out.println();
        }
    }
}

Sources