Lesson 16 — Abstract Classes

Abstraction

An abstract class defines a template: shared concrete implementation combined with abstract method stubs that subclasses must complete. It enforces a contract while providing a common foundation.

When to Use Abstract Classes

Use an abstract class when you have a family of related types that share some implementation but each must provide its own version of certain operations. A Shape has a colour (shared data), a describe() method (shared behaviour), but each subclass computes its area differently — area() must be abstract.

Key rules: you cannot instantiate an abstract class directly. Every abstract method must be overridden in any concrete (non-abstract) subclass, or that subclass must also be declared abstract.

JAVA — Abstract Shape class
public abstract class Shape {

    private String colour;

    // Regular constructor — subclasses call super(colour)
    public Shape(String colour) {
        this.colour = colour;
    }

    // Abstract methods — no body, subclass MUST implement these
    public abstract double area();
    public abstract double perimeter();

    // Concrete method — shared by all shapes (calls abstract area())
    public void describe() {
        System.out.printf("A %s %s — Area: %.2f cm² | Perimeter: %.2f cm%n",
            colour, getClass().getSimpleName(), area(), perimeter());
    }

    public String getColour() { return colour; }
}

Concrete Subclasses

JAVA — Concrete subclasses
public class Circle extends Shape {

    private double radius;

    public Circle(String colour, double radius) {
        super(colour);
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public double perimeter() {
        return 2 * Math.PI * radius;
    }
}

public class Rectangle extends Shape {

    private double width, height;

    public Rectangle(String colour, double width, double height) {
        super(colour);
        this.width  = width;
        this.height = height;
    }

    @Override
    public double area() { return width * height; }

    @Override
    public double perimeter() { return 2 * (width + height); }
}

public class Triangle extends Shape {

    private double base, height, side1, side2;

    public Triangle(String colour, double base, double height, double side1, double side2) {
        super(colour);
        this.base = base; this.height = height;
        this.side1 = side1; this.side2 = side2;
    }

    @Override
    public double area() { return 0.5 * base * height; }

    @Override
    public double perimeter() { return base + side1 + side2; }
}

Using Abstraction Polymorphically

JAVA — Polymorphic use
Shape[] shapes = {
    new Circle("gold", 5.0),
    new Rectangle("navy", 8.0, 3.0),
    new Triangle("ivory", 6.0, 4.0, 5.0, 5.0),
    new Circle("black", 2.5)
};

double totalArea = 0;
for (Shape s : shapes) {
    s.describe();          // polymorphic — each calls its own area/perimeter
    totalArea += s.area();
}
System.out.printf("%nTotal area of all shapes: %.2f cm²%n", totalArea);

// Cannot instantiate:
// Shape s = new Shape("red");  // Compile error: Shape is abstract

Abstract Classes vs Interfaces

Abstract classes can have fields, constructors, and concrete methods. An interface (Lesson 17) is a pure contract — it traditionally had only abstract methods (though Java 8+ allows default methods). Use an abstract class when there is genuine shared implementation; use an interface when you just need to define a contract.

JAVA — Abstract class vs interface
// Abstract class — has shared state and concrete methods
public abstract class Vehicle {
    protected int speed;
    protected int fuel;

    public void accelerate(int amount) { speed += amount; }  // concrete
    public abstract void fuelUp();                            // abstract
}

// Interface — pure contract, no state
public interface Trackable {
    String getLocation(); // abstract by default
}

// A class can extend ONE abstract class and implement MANY interfaces:
public class ElectricCar extends Vehicle implements Trackable {
    public void fuelUp()        { fuel = 100; System.out.println("Charged."); }
    public String getLocation() { return "GPS:25.7461,28.1870"; }
}

Practice Task

Your Turn

Design an abstract class Employee with fields name, employeeId, hourlyRate, and abstract method calculateMonthlyPay(). Implement concrete subclasses: FullTimeEmployee (160 hours/month fixed), PartTimeEmployee (variable hours, stored in field), ContractEmployee (fixed monthly contract amount). Add a shared concrete method printPaySlip() that calls calculateMonthlyPay(). Store all three in an Employee[] and print pay slips for all.

Common Mistakes

  • Trying to instantiate an abstract class with new — the compiler prevents it.
  • Forgetting to implement all abstract methods in a concrete subclass — it will be forced to be abstract itself.
  • Putting too much in the abstract class — abstract classes should provide shared foundation, not attempt to do everything.
  • Confusing abstract methods with concrete ones — abstract methods have no body (; after the signature), concrete ones have { }.
  • Calling an abstract method directly as if it has an implementation — it does not; the subclass version runs at runtime through polymorphism.

Professional Tip

The abstract class pattern is powerful because it enforces a contract (every Shape must have area() and perimeter()) while providing shared convenience (every Shape gets describe() for free). This is the Template Method design pattern — one of the most commonly used patterns in enterprise Java.

Mini Quiz

What happens if a concrete subclass does not implement all abstract methods?