Abstract Classes
Some classes exist only to be extended. There's no such thing as a plain "shape" — only circles, squares and triangles. An abstract class captures what its subclasses share, including some ready-made behaviour, while leaving certain methods for each subclass to fill in.
abstract classes and methods
abstract class Shape { abstract double area(); abstract double perimeter(); String summary() { return "%s: area %.2f, perimeter %.2f".formatted(getClass().getSimpleName(), area(), perimeter()); } } class Circle extends Shape { private final double r; Circle(double r) { this.r = r; } @Override double area() { return Math.PI * r * r; } @Override double perimeter() { return 2 * Math.PI * r; } } class Rectangle extends Shape { private final double w, h; Rectangle(double w, double h) { this.w = w; this.h = h; } @Override double area() { return w * h; } @Override double perimeter() { return 2 * (w + h); } } public class Main { public static void main(String[] args) { Shape[] shapes = {new Circle(1.5), new Rectangle(2, 5)}; for (Shape s : shapes) { System.out.println(s.summary()); } } }
Circle: area 7.07, perimeter 9.42 Rectangle: area 10.00, perimeter 14.00
- An abstract method has no body — just a signature ending with
;. Every concrete subclass must implement it. - A class with any abstract method must itself be declared
abstract. - Abstract classes can still have fields, constructors and normal methods — like
summary(), which relies on methods the subclasses provide.
You can't instantiate an abstract class
abstract class Shape { abstract double area(); } public class Main { public static void main(String[] args) { Shape s = new Shape(); } }
Main.java:7: error: Shape is abstract; cannot be instantiated
Forgetting to implement
abstract class Shape { abstract double area(); } class Triangle extends Shape { } public class Main { public static void main(String[] args) { } }
Main.java:5: error: Triangle is not abstract and does not override abstract method area() in Shape
The compiler guarantees every concrete shape has an area, so code using Shape can always call it safely.
The template method pattern
abstract class Report { final String generate() { return header() + "\n" + body() + "\n" + footer(); } String header() { return "=== " + title() + " ==="; } String footer() { return "--- end of report ---"; } abstract String title(); abstract String body(); } class SalesReport extends Report { @Override String title() { return "Monthly Sales"; } @Override String body() { return "Revenue: 1055.02 EUR\nOrders: 14"; } } public class Main { public static void main(String[] args) { System.out.println(new SalesReport().generate()); } }
=== Monthly Sales === Revenue: 1055.02 EUR Orders: 14 --- end of report ---
The abstract class fixes the overall algorithm (generate, made final so it can't be changed) and lets subclasses supply the varying steps. Frameworks use this pattern constantly.
Constructors in abstract classes
abstract class Vehicle { protected final String plate; Vehicle(String plate) { this.plate = plate; } abstract int wheels(); String describe() { return plate + " (" + wheels() + " wheels)"; } } class Motorbike extends Vehicle { Motorbike(String plate) { super(plate); } @Override int wheels() { return 2; } } public class Main { public static void main(String[] args) { System.out.println(new Motorbike("AA-12-BB").describe()); } }
AA-12-BB (2 wheels)
An abstract class's constructor runs as part of creating each subclass object, so it can initialise shared fields.
Abstract class or interface?
Interfaces (next lesson) also declare methods for others to implement, and a class can implement many of them. Prefer an interface to define a capability; use an abstract class when subclasses share state (fields) or substantial common code, and they really form one family.
Try it yourself
Model payment methods that share a fee calculation but each define their own percentage.
Card 203.00 Bank transfer 200.00