Concepts / Abstract Classes

Abstract Classes

An abstract class is declared with the abstract modifier and is meant to be subclassed rather than instantiated on its own.

  • Programming

A Drawing Program Full of Shapes

Imagine a drawing program that holds several kinds of shapes on screen: rectangles, ovals, and rounded rectangles. Each of these knows how to paint itself, and each does so through its own redraw() method. All of these shapes share a common superclass, which we can call Shape. The design question is what redraw() should do inside Shape itself.

This article follows that question to its answer. The answer is a useful idea in object-oriented design: some classes exist only to describe what their subclasses have in common, and they are never meant to be turned into objects. Working through why that is true explains the purpose of abstract classes and abstract methods. As you read, keep one question in mind throughout: which parts of Shape are complete, and which parts are left for someone else to finish?

Tracing the Shape Class

The source builds its discussion around a short Java program based on a Shape class. Trace the class below before reading the explanation. The setColor method is fully written: it stores a new color in the color instance variable and then calls redraw. The redraw method is different. It has no body at all, because its declaration ends with a semicolon.

java

Read the class with the trace in mind. The part that is complete is setColor: every subclass of Shape inherits working color-changing behavior without writing it again. The part that is unfinished is redraw: the class declares that the operation exists, but it does not say how a shape is drawn. That unfinished part is what the rest of this article is about.

Why Shape Cannot Draw Itself

Whenever a particular shape must draw itself, the redraw() method belonging to that shape's own class is the one that runs. The redraw() inside Shape is never the one doing the drawing for a rectangle or an oval. That raises a puzzle: if Shape's version will not really be used, what should it contain?

The honest answer is to leave the body empty. Shape stands for the general notion of a shape, and there is no real way to draw a general notion. Only specific shapes, such as a rectangle or an oval, can actually be drawn. Leaving the body blank is therefore not laziness. It reflects the fact that no sensible drawing instructions exist at this level of the hierarchy.

But the method cannot simply vanish. Other code in Shape, such as setColor(), calls redraw(). A program may also hold a variable declared as Shape and call redraw() on it. The compiler checks these calls against the declared type, not against the object that happens to be stored there. If Shape had no redraw() at all, those calls would be rejected. So the method must be declared in Shape even though its body has nothing to contribute.

Key point

A superclass may need a method that has no meaningful body because the method is part of the shared vocabulary of every subclass, even though only the subclasses can say how the operation actually works.

Declaring the Abstract Class and Method

The two declarations in this section use the same keyword, abstract, in different places. The keyword changes what the compiler will accept. Each part is covered below.

Calling redraw Through a Shape Variable

Programs need variables whose type is Shape. Such a variable can refer to a rectangle, an oval, or any other specific shape, and a call made through it always reaches an object from one of Shape's subclasses. The sequence below shows the flow for one call. The caller asks for the operation through the Shape variable, and the work is done by the subclass object.

Programcalls setColorShape variabledeclared type ShapeRectangle objectconcrete subclasss.setColor redredraw runs Rectangle version
When code calls the method through a superclass reference, which subclass implementation runs, and why can the superclass not supply a real body itself?
java
java
Output
Drawing a rectangle in red

Trace the run step by step. The variable s is declared as Shape, but the object it refers to is a Rectangle. The call s.setColor("red") runs the complete method inherited from Shape. That method stores red in color and then calls redraw(). Because the object is a Rectangle, the redraw() belonging to Rectangle is the one that runs, and it prints the message shown in the output. Shape never supplies a drawing body of its own.

Where a Hierarchy Breaks the Rules

The hierarchy has a clear shape. Shape is the general parent, and Rectangle, Oval, and RoundRect are specific children. The top node can never be built directly, while each lower node can. An abstract method declared in the top node must therefore be given a body in each lower node before an object of that lower type can be created and used. The diagram below marks the place where that chain of obligations breaks.

Shapeabstract, redraw has nobodyRectangleconcrete, redrawimplementedOvalconcrete, redrawimplementedRoundRectconcrete, redrawimplementedTriangleredraw missingextendsextendsextendsextends
In a class hierarchy with an abstract superclass, which concrete class is missing the implementation of an inherited abstract method, and where does the chain of obligations break?

Suppose the programmer adds a new shape, a triangle, by writing a Triangle class that extends Shape. Because Shape declares redraw() as abstract, Triangle must supply a redraw() implementation before it can be a concrete class. The class below omits that method.

java

Triangle is not concrete in its current form, so the program will not accept an attempt to create a Triangle object. The fix is to add a redraw() method to Triangle. The expectation that Triangle would be finished was not met at the point where the implementation is missing, and that is the point to inspect.

Abstract Versus Concrete Classes

The two kinds of class sit at different levels of a hierarchy. An abstract class sits above and describes common properties. Concrete classes sit below it and supply everything needed to make a working object. The table summarizes the contrast.

FeatureAbstract class (Shape)Concrete class (Rectangle)
DeclarationUses the abstract modifierNo abstract modifier
Can create objects with newNoYes
Can be the parent of other classesYes, that is its purposeYes, but it is also complete
Abstract methodsMay declare them with a semicolon instead of a bodyMust provide a body for every inherited abstract method
RoleShares common properties and partial implementationSupplies everything needed for a working object

Abstract and concrete classes compared by what they declare and what can be constructed from them

Mistakes Beginners Make With Abstract Classes

  • Common mistake

    Trying to create an object of an abstract class

    What it looks like
    Shape s = new Shape();
    Why it fails

    An abstract class is not used to construct objects. It exists as a basis for making subclasses and to express the properties they share.

    Do this instead

    Create an object of a concrete subclass such as new Rectangle(), and store it in a Shape variable if needed.

  • Common mistake

    Deleting the abstract method declaration because its body is empty

    What it looks like
    public class Shape { public void setColor(String c) { color = c; redraw(); } }
    Why it fails

    The call to redraw() still needs a declaration in Shape, and the compiler checks calls against the declared type.

    Do this instead

    Keep the declaration as public abstract void redraw(); so the call is legal and the subclasses are obliged to implement it.

  • Common mistake

    Assuming the superclass version of an abstract method will run

    Why it fails

    The abstract method has no body, and the redraw() belonging to the object's own class is the one that runs.

    Do this instead

    Look at the concrete subclass of the object being used and check its implementation.

  • Common mistake

    Forgetting that a concrete subclass must implement every inherited abstract method, not just the ones it wants

Finding the Missing Implementation

What do you think happens?

Given that Shape is declared abstract, what happens when a program runs Shape s = new Shape();?

  • It creates a Shape object whose redraw() does nothing

  • The compiler rejects it because Shape is abstract

Reveal answer
Answer:

The compiler rejects it because Shape is abstract.

An abstract class is not used to construct objects. Only concrete classes can be used to create objects, so new Shape() is not allowed. Objects must be created from a concrete subclass such as Rectangle.

EASY

A class Circle extends Shape and declares no redraw() method. Is Circle concrete? Can you write new Circle()? What single change would make Circle usable?

Hints
  • Ask whether Circle is declared abstract. If it is not, it is concrete.
  • Check whether Circle supplies an implementation for every abstract method it inherits from Shape.
  • Adding a redraw() method with a body to Circle is the change that satisfies the obligation.

Recapping the Three Ideas

  1. 1
    The abstract modifier marks a class as meant to be subclassed rather than instantiated.
  2. 2
    An abstract method has no body and ends with a semicolon, and it is declared so that calls through the superclass type are legal.
  3. 3
    Every concrete subclass must supply an implementation for each abstract method it inherits, and a missing implementation is the most likely place to look when a hierarchy behaves unexpectedly.

Key Takeaways

  • The abstract modifier on a class marks it as incomplete on purpose, so it is subclassed rather than used to construct objects.
  • An abstract method is declared with the abstract keyword and ends with a semicolon instead of a body, and it stays declared so calls through the superclass type are legal.
  • A superclass may hold an abstract method with no meaningful body because only the concrete subclasses can say how the operation works.
  • Every concrete subclass must implement each inherited abstract method, so a missing implementation is the first place to look when a hierarchy misbehaves.