Inheritance & Polymorphism

Abstract Classes (Introduction)

15 min Lesson 8 of 14

Abstract Classes (Introduction)

So far every class you have written can be instantiated — you can call new Dog(), new Circle(), and so on. But sometimes a class exists purely as a blueprint that other classes must fill in. It makes no sense to create a bare Shape object if you never intend to draw it directly. Java gives you a precise tool for this: the abstract class.

What Does abstract Mean?

Adding the abstract keyword to a class declaration tells the compiler two things:

  1. This class cannot be instantiated directly. Calling new Shape() is a compile-time error.
  2. It may contain abstract methods — method signatures with no body — that subclasses are required to implement.
Key idea: An abstract class models an incomplete concept. It defines what subclasses must do (via abstract methods) and may also provide shared behaviour that subclasses inherit for free (via concrete methods).

Declaring an Abstract Class and Abstract Methods

Here is the syntax:

public abstract class Shape { // abstract method: declares WHAT, not HOW public abstract double area(); // concrete method: shared implementation all subclasses inherit public String describe() { return "I am a shape with area " + area(); } }

The method area() has no body — just a semicolon after the signature. Every concrete subclass must override it, or the compiler refuses to compile that subclass.

Creating Concrete Subclasses

A concrete class is simply one that is not abstract. It provides real implementations for all abstract methods it inherits:

public class Circle extends Shape { private final double radius; public Circle(double radius) { this.radius = radius; } @Override public double area() { return Math.PI * radius * radius; } } public class Rectangle extends Shape { private final double width; private final double height; public Rectangle(double width, double height) { this.width = width; this.height = height; } @Override public double area() { return width * height; } }

Now you can use them:

public class Main { public static void main(String[] args) { Shape c = new Circle(5); Shape r = new Rectangle(4, 6); System.out.println(c.describe()); // I am a shape with area 78.53981633974483 System.out.println(r.describe()); // I am a shape with area 24.0 } }
Polymorphism at work: Both objects are stored as Shape references. The call to describe() is defined once in the abstract class, yet it internally calls the correct area() implementation at runtime — that is dynamic dispatch from Lesson 5 in action.

Providing a Partial Implementation

One of the greatest strengths of abstract classes is that they can do some of the work for subclasses. Suppose every shape has a colour that you track the same way for all of them:

public abstract class Shape { private final String colour; public Shape(String colour) { this.colour = colour; } public String getColour() { return colour; } // subclasses must still provide this public abstract double area(); public String describe() { return colour + " shape, area = " + String.format("%.2f", area()); } }
public class Circle extends Shape { private final double radius; public Circle(String colour, double radius) { super(colour); // chain to the abstract class constructor this.radius = radius; } @Override public double area() { return Math.PI * radius * radius; } }

The colour field, its getter, and the describe() method are written once and shared. Only the part that truly varies — area() — is left for each subclass to define.

Rules to Remember

  • A class with at least one abstract method must be declared abstract.
  • An abstract class may have zero abstract methods — it is still un-instantiable, which can be useful on its own.
  • A subclass that does not implement all inherited abstract methods must itself be declared abstract.
  • Abstract classes can have constructors, fields, and static members — they just cannot be instantiated directly.
Common pitfall: Forgetting to implement every abstract method in a concrete subclass. The compiler will tell you which method is missing — read the error carefully, it always names the method.

Abstract Classes vs Interfaces — a First Glance

You will learn about interfaces soon. For now, the short distinction is:

  • An abstract class can have fields, constructors, and both abstract and concrete methods. Use it when subclasses share state or common behaviour.
  • An interface defines a pure contract — no state. Use it when you want to describe a capability that unrelated classes can share.

Summary

Abstract classes sit between a fully concrete class and a pure interface. They let you define what subclasses must do (abstract methods) while also providing shared implementation (concrete methods and fields). The abstract keyword on the class prevents accidental instantiation, and the abstract keyword on a method forces every concrete subclass to supply a real body. This pattern is the foundation of the shape hierarchy you will build in the final lesson of this tutorial.