Abstract Class
An abstract class is a class that cannot be instantiated directly — it exists to be extended. It lets you define a common blueprint with some behavior already filled in and some left intentionally incomplete for subclasses to provide.
Why Abstract Classes Exist
Sometimes you want to model a concept that is too general to be concrete. A Shape has an area, but there is no single formula that covers every shape. An abstract class lets you say: “every Shape must know how to compute its area — but I’m not going to write that logic here.”
This is the heart of abstraction: hiding irrelevant details and enforcing a contract.
Declaring an Abstract Class
Use the abstract keyword before the class keyword. An abstract class can have:
- Abstract methods — declared without a body (subclasses must implement them)
- Concrete methods — fully implemented, shared by all subclasses
- Fields, constructors, and static members — just like any regular class
abstract class Shape {
String color; // regular field
// Constructor — yes, abstract classes can have constructors!
Shape(String color) {
this.color = color;
}
// Abstract method — no body, subclass must implement
abstract double area();
// Concrete method — shared by all shapes
void display() {
System.out.println("Color: " + color + ", Area: " + area());
}
}
Note: If a class contains even one abstract method, the class itself must be declared
abstract. A concrete class that extends an abstract class must implement all abstract methods — or be declared abstract itself.
Implementing an Abstract Class
A subclass extends the abstract class using extends and overrides every abstract method:
class Circle extends Shape {
double radius;
Circle(String color, double radius) {
super(color); // call abstract class constructor
this.radius = radius;
}
@Override
double area() {
return Math.PI * radius * radius;
}
}
class Rectangle extends Shape {
double width, height;
Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}
@Override
double area() {
return width * height;
}
}
public class Main {
public static void main(String[] args) {
Shape c = new Circle("Red", 5);
Shape r = new Rectangle("Blue", 4, 6);
c.display();
r.display();
}
}
Output:
Color: Red, Area: 78.53981633974483
Color: Blue, Area: 24.0
Notice that c and r are declared as type Shape. The JVM dispatches area() to the correct subclass at runtime — this is runtime polymorphism in action.
Tip: You cannot write
new Shape("Red")— that will cause a compile-time error: “Shape is abstract; cannot be instantiated.”
Abstract Classes with Constructors
Even though you can’t create an instance of an abstract class directly, constructors are still useful. Every subclass constructor calls super(...), which triggers the abstract class’s constructor to initialize shared fields.
abstract class Animal {
String name;
Animal(String name) {
this.name = name;
System.out.println("Animal created: " + name);
}
abstract void makeSound();
}
class Dog extends Animal {
Dog(String name) {
super(name); // triggers Animal's constructor
}
@Override
void makeSound() {
System.out.println(name + " says: Woof!");
}
}
Output:
Animal created: Buddy
Buddy says: Woof!
Concrete Methods in Abstract Classes
One major advantage over interfaces is that abstract classes can contain real, implemented methods. This lets you provide default behavior that subclasses can use or override:
abstract class Vehicle {
int speed;
Vehicle(int speed) {
this.speed = speed;
}
abstract String fuelType(); // must override
// Shared concrete behavior
void accelerate(int amount) {
speed += amount;
System.out.println("Speed: " + speed + " km/h");
}
}
class ElectricCar extends Vehicle {
ElectricCar(int speed) {
super(speed);
}
@Override
String fuelType() {
return "Electric";
}
}
Rules at a Glance
| Rule | Details |
|---|---|
| Keyword | abstract class ClassName |
| Can be instantiated? | No |
| Can have abstract methods? | Yes (zero or more) |
| Can have concrete methods? | Yes |
| Can have constructors? | Yes |
Can have static methods? | Yes |
Can have final methods? | Yes (subclasses cannot override them) |
| Can extend another class? | Yes (including another abstract class) |
| Can implement interfaces? | Yes |
Warning: A
final abstract classis a compile error —finalprevents subclassing, which makes an abstract class useless.
Abstract Class vs Interface — When to Choose Which
Both enforce contracts. Use an abstract class when:
- You want to share code (fields, constructors, concrete methods) across subclasses
- Your subtypes represent a genuine “is-a” hierarchy (a
Dogis anAnimal) - You need non-public members
Use an interface when you want pure behavior contracts that any class can adopt regardless of hierarchy. See the full comparison at Abstract Class vs Interface.
| Feature | Abstract Class | Interface |
|---|---|---|
| Multiple inheritance | No (single extends) | Yes (multiple implements) |
| Fields | Any type | Only public static final |
| Constructors | Yes | No |
| Access modifiers | Any | Methods are public by default |
| Default methods (Java 8+) | Concrete methods always | default keyword required |
The Template Method Pattern
Abstract classes shine in the Template Method design pattern: the abstract class defines the overall algorithm skeleton in a concrete method, calling abstract steps that subclasses fill in:
abstract class DataProcessor {
// Template method — the algorithm skeleton
final void process() {
readData();
processData();
writeData();
}
abstract void readData();
abstract void processData();
// Default step — subclasses may override if needed
void writeData() {
System.out.println("Writing output to default location.");
}
}
class CSVProcessor extends DataProcessor {
@Override
void readData() { System.out.println("Reading CSV file..."); }
@Override
void processData() { System.out.println("Parsing CSV rows..."); }
}
public class Main {
public static void main(String[] args) {
new CSVProcessor().process();
}
}
Output:
Reading CSV file...
Parsing CSV rows...
Writing output to default location.
The final on process() means subclasses cannot change the order of steps — only the steps themselves.
Under the Hood
When the JVM loads an abstract class, it creates a normal Class object — the abstract flag is simply stored in the class’s access flags in the bytecode (ACC_ABSTRACT). Abstract methods have the same flag set, and their method entries in the constant pool have no Code attribute.
At runtime, the JVM uses a vtable (virtual method table) for each concrete subclass. Every abstract method slot gets replaced by the concrete implementation provided by the subclass. Calling shape.area() does not go through a branch or lookup — it goes directly to the correct vtable slot, making virtual dispatch very fast. Read more in vtable & Dynamic Dispatch.
Memory-wise, you cannot allocate an instance of an abstract class, so the JVM never reserves heap space for an “abstract object.” The constructor bytecode is still compiled and stored; it is simply called via invokespecial from a concrete subclass constructor.
Tip: Use
javap -verbose ClassName(see the javap tool) on both an abstract class and one of its concrete subclasses to see theACC_ABSTRACTflag and how vtable slots are filled.
Related Topics
- Abstraction — the broader OOP concept that abstract classes implement
- Interface — the other main tool for enforcing contracts in Java
- Abstract Class vs Interface — a detailed side-by-side comparison to help you choose
- Runtime Polymorphism — how the JVM dispatches method calls to concrete subclasses at runtime
- Inheritance — the foundation that makes abstract classes possible
- final Keyword — how
finalmethods inside abstract classes prevent overriding