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.
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
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
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.
// 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?