Learn Java

Lesson 23Phase 3: Abstraction and design

Abstract classes

Classes you cannot instantiate, methods with no body, and forcing subclasses to fill in the blanks.

By Updated 55 min

  • abstract class
  • abstract method
  • cannot instantiate
  • template
  • partial implementation

You can now write a parent class, extend it, override its methods, and point a parent-typed variable at a child object so the right behavior runs. One awkward gap is left. Some parent classes describe a kind of thing that should never exist on its own. What sound does a plain, generic Animal make? There is no honest answer, and yet nothing has stopped you from writing new Animal(). This lesson closes that gap.

By the end of this lesson
  • Explain why some classes should exist only to be extended, never built directly.
  • Mark a class abstract, and read the compiler's message when code calls new on it.
  • Write an abstract method with no body, and say what it forces every subclass to do.
  • Tell a concrete subclass from one that is still abstract because it left a method unwritten.
  • Use an abstract class as a template: shared fields, a constructor and finished methods in the parent, and abstract steps for each subclass to fill in.
From before
You will lean on four ideas you already have: inheritance and the is-a relationship, overriding and super, constructors, and polymorphism, where one parent-typed variable can drive different behavior per object. Abstract classes are a small addition on top of all of that. Interfaces, a close relative, come in the next lesson.

A parent that should never be built directly#

Picture the animal family you have been building. Animal is the parent. Dog, Cat and Cow extend it, and each one overrides speak() to return its own sound. Polymorphism lets you keep them all in one Animal[] and call speak() on each.

That all works, but there is a quiet problem in the parent. Animal exists to be a shared shape for real animals. On its own it is not a thing you could point at in the world: a real animal is always a dog, a cat or some other particular kind. So this program makes no sense, even though Java has not objected to it yet:

Zoo.java
1public class Main {
2 public static void main(String[] args) {
3 Animal a = new Animal();
4 System.out.println(a.speak());
5 }
6}
7
8class Animal {
9 String speak() {
10 return "...";
11 }
12}
Prints
›...

It prints ..., a sound made up so that Animal would compile. That fake body is a warning sign: it exists only to answer a question nobody should be able to ask. The honest statement is that a generic animal has no sound, because a generic animal is not a real thing. You want the language to enforce that, so nobody can build one by accident.

Key idea

Some classes exist only to be a shared base for others. They describe a category, not a particular thing. You want to stop anyone building one directly, and to get rid of every fake placeholder body.

Marking a class abstract#

The keyword is abstract, written in front of class. To start with, it means one thing: no object of this class can be made with new. The class can still be extended, and its subclasses can still be built. Only a direct new is forbidden. This program tries it anyway:

Zoo.java (does not compile)
read along
1public class Main {
2 public static void main(String[] args) {
3 Animal a = new Animal();
4 System.out.println("This line never runs.");
5 }
6}
7
8abstract class Animal {
9 String name;
10}
Output
Main.java:3: error: Animal is abstract; cannot be instantiated
        Animal a = new Animal();
                   ^
1 error

The compiler refuses before the program ever runs. The message names the line, 3, and the reason: Animal is abstract; cannot be instantiated. To instantiate a class is to make an object of it, an instance. The language caught the nonsense for you.

The variable type Animal is still perfectly legal. A variable can be typed Animal and point at a Dog; what you cannot do is make a bare Animal object for it to point at. Here is the legal version, where Dog extends the abstract Animal:

Zoo.java
1public class Main {
2 public static void main(String[] args) {
3 Animal pet = new Dog();
4 pet.name = "Rex";
5 System.out.println(pet.name + " is an animal");
6 }
7}
8
9abstract class Animal {
10 String name;
11}
12
13class Dog extends Animal {
14}
Prints
›Rex is an animal

It prints Rex is an animal. Step through it and look at the memory view: the variable pet is typed Animal, but the one object it points at is tagged Dog #1. An abstract class can be the type of a variable, but never the class of an object.

Abstract is about intent
Marking a class abstract changes nothing about how the objects of its subclasses behave. It is a promise written into the type system: this class is a base, not a destination. It tells every other programmer, including you next month, what the class is for, and it lets the compiler hold everyone to it.
Quick check
Animal is abstract and Dog extends it. Which line does not compile?
Coming from Python?
Python's abc module gives the same idea: a class that inherits from ABC and marks a method with @abstractmethod cannot be instantiated. Python finds out when the program runs and raises a TypeError; Java refuses to compile the program at all.

An abstract method: a blank every subclass must fill#

Forbidding new Animal() is half the idea. The other half: you can remove the fake speak() body entirely and keep only the method's first line. That is an abstract method: a method with a return type, a name and parameters, ending in a semicolon where the body would be.

Animal.java
1public class Main {
2 public static void main(String[] args) {
3 System.out.println("compiles fine; Animal cannot be built");
4 }
5}
6
7abstract class Animal {
8 String name;
9
10 abstract String speak();
11}
Prints
›compiles fine; Animal cannot be built

Read abstract String speak(); as a sentence: “every animal can speak and hands back a Stringwhen it does, but I, the parent, will not say what the sound is. That is each subclass's job.” There are no braces, because there is no code. The method is a hole with an exact shape, and the shape is the contract every subclass must meet. The program only prints a line, which shows that the declaration compiles.

Key idea

An abstract method is a promise with no code behind it. It says that every concrete subclass must provide this exact method, and the compiler will not let a subclass forget.

A class with an abstract method must be abstract

One rule follows on its own, and it trips people up: a class with even one abstract method must itself be declared abstract. It has to be. If Animal has an unanswered speak(), then Animal is incomplete, and building one would give you the impossible object again. Leave the keyword off the class and javac stops you:

Broken.java (does not compile)
read along
1public class Main {
2 public static void main(String[] args) {
3 System.out.println("This line never runs.");
4 }
5}
6
7class Animal {
8 abstract String speak();
9}
Output
Main.java:7: error: Animal is not abstract and does not override abstract method speak() in Animal
class Animal {
^
1 error

The message reads oddly at first: Animal is not abstract and does not override abstract method speak() in Animal. javac expects a class that is not abstract to be complete, finds a method with no body, and says so. The fix is one word: abstract class Animal.

Concrete subclasses fill every blank#

A subclass that gives a real body to every abstract method it inherits is called a concrete class. Concrete means complete: no holes left, so you may build it with new. Here are three concrete animals, each overriding speak(), all driven through one parent-typed array.

Main.java
1public class Main {
2
3 public static void main(String[] args) {
4 Animal[] zoo = new Animal[3];
5 zoo[0] = new Dog("Rex");
6 zoo[1] = new Cat("Mimi");
7 zoo[2] = new Cow("Daisy");
8
9 int i = 0;
10 while (i < zoo.length) {
11 zoo[i].introduce();
12 i = i + 1;
13 }
14 }
15}
16
17abstract class Animal {
18 String name;
19
20 abstract String speak();
21
22 void introduce() {
23 System.out.println(name + " says " + speak());
24 }
25}
26
27class Dog extends Animal {
28 Dog(String n) {
29 name = n;
30 }
31 String speak() {
32 return "Woof";
33 }
34}
35
36class Cat extends Animal {
37 Cat(String n) {
38 name = n;
39 }
40 String speak() {
41 return "Meow";
42 }
43}
44
45class Cow extends Animal {
46 Cow(String n) {
47 name = n;
48 }
49 String speak() {
50 return "Moo";
51 }
52}
Prints
›Rex says Woof
›Mimi says Meow
›Daisy says Moo

It prints Rex says Woof, Mimi says Meow and Daisy says Moo. Step through and watch the loop. The array is typed Animal[], but the memory view shows what each slot really holds: Dog #1, Cat #1, Cow #1. When introduce() calls speak(), polymorphism picks the version that belongs to the actual object. The parent never knew the sound and never needed to. It relied on every concrete subclass supplying one, and the abstract method made that reliance safe.

The contract does the heavy lifting
Because speak() is abstract, the compiler guarantees that Dog, Cat and Cow each have it. So inside introduce(), calling speak() is always safe. You get to write code in the parent that depends on behavior the parent itself does not provide.

What if a subclass forgets?

Suppose a parent declares two abstract methods, start() and move(), and a subclass writes only start(). That subclass still has a hole, so it is still incomplete, so it is still abstract, whether you meant it to be or not. Java makes you either write the missing method or mark the subclass abstract too. You cannot quietly leave a blank and then build the object.

HalfDone.java (does not compile)
read along
1public class Main {
2 public static void main(String[] args) {
3 Robot r = new Robot();
4 r.start();
5 }
6}
7
8abstract class Machine {
9 abstract void start();
10 abstract void move();
11}
12
13class Robot extends Machine {
14 void start() {
15 System.out.println("booting");
16 }
17 // move() is missing, so Robot still has a blank
18}
Output
Main.java:13: error: Robot is not abstract and does not override abstract method move() in Machine
class Robot extends Machine {
^
1 error
Put the lines in orderSquares.java
Put the lines in order: an abstract Shape with an abstract area(), a Square that fills it in, and a main that prints the area of a 3 by 3 square through a Shape variable. The program prints 9.

Tap a line, then tap where it should go. Or use the arrows.

Abstract classes are not empty shells#

An abstract class can hold ordinary members alongside its abstract ones. It can have fields, it can have constructors, and it can have finished methods with normal bodies. So one abstract base class can share code and force a contract at the same time. This is the partial implementation an abstract class offers: the parts every subclass shares are written once, and the parts that differ are left for each subclass.

You already saw this above: introduce() was a finished method inside the abstract Animal, and every subclass got it without rewriting it. Here is the same pattern again. An abstract Shape declares one abstract method, area(), and provides a finished describe() that all shapes share.

Main.java
1public class Main {
2
3 public static void main(String[] args) {
4 Shape[] shapes = new Shape[2];
5 shapes[0] = new Circle(2.0);
6 shapes[1] = new Rectangle(3.0, 4.0);
7
8 int i = 0;
9 while (i < shapes.length) {
10 shapes[i].describe();
11 i = i + 1;
12 }
13 }
14}
15
16abstract class Shape {
17 String name;
18
19 abstract double area();
20
21 void describe() {
22 System.out.println(name + " has area " + area());
23 }
24}
25
26class Circle extends Shape {
27 double radius;
28 Circle(double r) {
29 radius = r;
30 name = "Circle";
31 }
32 double area() {
33 return 3.14 * radius * radius;
34 }
35}
36
37class Rectangle extends Shape {
38 double width;
39 double height;
40 Rectangle(double w, double h) {
41 width = w;
42 height = h;
43 name = "Rectangle";
44 }
45 double area() {
46 return width * height;
47 }
48}
Prints
›Circle has area 12.56
›Rectangle has area 12.0

This prints Circle has area 12.56, then Rectangle has area 12.0. Look at how the work is split. Shape owns the name field and the whole describe() method, written once and shared by both shapes. What Shapewill not do is compute an area, because there is no single formula for “a shape”. So area() is abstract, and each subclass brings its own formula.

Where this contrast is going
A type made only of abstract methods, with no shared code, is common enough that Java has a separate construct for it: the interface, in the next lesson. The lesson after that, interface vs abstract class, is all about choosing between the two. For now, hold this thought: an abstract class is the right tool when you have real shared code to give your subclasses, not only a list of promises.

Constructors in an abstract class

It can feel contradictory at first. If you cannot call new on an abstract class, why would it have a constructor? Because the constructor is not for building a bare Account. It runs as part of building a subclass. When you write new Savings(...), Java builds the Savingsobject, and the parent's constructor sets up the part of the object the parent is responsible for. The subclass reaches it with the super(...) call from inheritance. Both constructors below print a line. Decide the order before you run it.

Predict the outputAccounts.java
1public class Main {
2 public static void main(String[] args) {
3 Account a = new Savings("Aisha");
4 a.describe();
5 }
6}
7
8abstract class Account {
9 String owner;
10
11 Account(String owner) {
12 this.owner = owner;
13 System.out.println("Account constructor");
14 }
15
16 abstract String kind();
17
18 void describe() {
19 System.out.println(owner + " has a " + kind() + " account");
20 }
21}
22
23class Savings extends Account {
24 Savings(String owner) {
25 super(owner);
26 System.out.println("Savings constructor");
27 }
28
29 String kind() {
30 return "savings";
31 }
32}

main builds one Savings account and asks it to describe itself. What are the three lines, in order?

It prints Account constructor, then Savings constructor, then Aisha has a savings account. new Savings("Aisha") starts the Savings constructor, whose first line, super(owner), runs the Account constructor. That one stores the owner and prints its line, and only then does the Savings constructor carry on and print its own. Step through it in the playground and you will see both constructors on the call stack at once. You never built a bare Account, yet its constructor ran, setting up the shared field for the subclass.

A template: the parent fixes the steps#

Look again at describe(): a finished method in the parent that calls an abstract method the children fill in. That shape has a name, the template method pattern. The parent writes the outline of a job, the steps and their order, and leaves some of the steps blank. Each subclass fills in the blanks and never touches the outline. A form works the same way: the layout is printed once, and each person writes in their own answers.

Here every document prints the same way: a header line with its title, then its body, then an end line. print() is the template. title() and printBody() are the blanks.

Documents.java
1public class Main {
2 public static void main(String[] args) {
3 Document[] docs = { new Receipt(), new Invitation() };
4 for (Document d : docs) {
5 d.print();
6 }
7 }
8}
9
10abstract class Document {
11 void print() {
12 System.out.println("== " + title() + " ==");
13 printBody();
14 System.out.println("== end ==");
15 }
16
17 abstract String title();
18
19 abstract void printBody();
20}
21
22class Receipt extends Document {
23 String title() {
24 return "Receipt";
25 }
26
27 void printBody() {
28 System.out.println("2 coffees: 7.0");
29 }
30}
31
32class Invitation extends Document {
33 String title() {
34 return "Invitation";
35 }
36
37 void printBody() {
38 System.out.println("Dear Mia, please come to the party.");
39 }
40}
Prints
›== Receipt ==
›2 coffees: 7.0
›== end ==
›== Invitation ==
›Dear Mia, please come to the party.
›== end ==

Both documents come out in the same frame: == Receipt ==, the body, == end ==, then the same for the invitation. Step through print()and watch the call stack: the steps run in the parent's order, and each blank jumps into the subclass for its answer. A new kind of document only has to say what goes in the blanks. The frame, and any fix to it, lives in one place.

Locking the template
A subclass could still override print() itself and change the order. To forbid that, a parent marks the template method final, which you meet in static and final.
ExercisePayroll.java
Every employee gets a payslip line, but salaried and hourly staff are paid differently. Make monthlyPay() in Employee an abstract method with no body, then give each subclass its own monthlyPay(): a salaried employee earns yearly / 12 a month, and an hourly one earns hours * rate.

Abstract versus concrete, side by side#

Keep these two words straight and most of the confusion goes away. Abstract means incomplete on purpose and not buildable. Concrete means complete and buildable.

Abstract class
Concrete class
Marked with the keyword abstract.
An ordinary class, no special keyword.
Cannot be built: new Animal() does not compile.
Built freely with new.
May have abstract methods (no body) that subclasses must fill in.
Has a real body for every method it owns or inherits.
Exists to be extended. It is a base, not a destination.
Exists to be built. It is a real thing.
Can still hold fields, constructors and finished methods.
Holds fields, constructors and finished methods too.
A subclass that leaves a blank unfilled must stay abstract.
A subclass that fills every blank can be concrete.
Key idea

An abstract class gives you two things in one base: shared code that subclasses inherit, and a list of blanks that subclasses are forced to fill.

A short checklist for writing one

  1. 1Notice a parent that should never be built, or a method that has no sensible default body. Mark the class abstract.
  2. 2For each behavior every subclass must provide but the parent cannot, write abstract ReturnType name(parameters); with a semicolon and no body.
  3. 3Put the code the subclasses share (fields, constructors and finished methods) in the abstract class, so every subclass inherits it.
  4. 4In each concrete subclass, override every abstract method with a real body. Only then can you call new on it.

Try it

Answer each one in your head or on paper first, then open the answer.

1
In one sentence, why is new Animal() forbidden once Animal is marked abstract, while Animal a = new Dog(); is still allowed?
2
A class declares abstract void start(); but will not compile. What single change fixes it?
3
Look at the Shape example. describe() calls area(), yet Shape never gives area() a body. Why is that call safe?
4
Class Vehicle is abstract, with two abstract methods, start() and stop(). Class Car extends Vehicle writes only start(). Can you write new Car()? Why or why not?
5
In Documents.java, a new class Ticket extends Document. Which methods must it write, and which does it get for free?

Practice

Work through these on your own. Each one builds on this page.

1
Open the Shape example in the playground and add a third concrete subclass, Triangle, with fields base and height and an area() of 0.5 * base * height. Add it to the array, which now needs three slots, and check its line of output.
2
Break the rules on purpose, then explain in one sentence each what the compiler says. First, add Shape s = new Shape(); to main. Second, in a fresh file, write abstract void move(); inside a class that is not marked abstract. Run both with Java on your own computer, since the compiler is what catches them.
3
Design an abstract Character for a small game. Every character has a name and a number of health points, and a finished method takeHit(int damage) that lowers the health and prints what is left. Each kind of character attacks differently, so int attackPower() is abstract. Write two concrete characters, such as a Knight and a Wizard, and a main in which each one attacks the other once.
4
In two or three sentences, explain what marking a class abstract lets the compiler catch that you would otherwise only discover when the program ran. Use the fake "..." sound from the start of this page as your example.

Further reading#

Spotted a mistake, or a part that did not make sense? Report it by email