Dataslope logoDataslope

Inheritance and Polymorphism

Reusing behavior through "is-a" relationships, and letting one method call do different things at runtime

So far each of our classes has stood alone. But many real-world modeling problems involve families of related things: shapes, employees, accounts, vehicles, events. Java lets one class extend another, inheriting its fields and methods. This is inheritance, and it's one of the most powerful — and most over-used — features of OOP.

"Is-a" relationships

Inheritance models an is-a relationship. A Dog is an Animal. A SavingsAccount is a BankAccount. A Circle is a Shape.

If you cannot honestly say "X is a Y" in plain English, do not use inheritance to connect them.

The arrow with the open triangle means "extends." Circle and Rectangle are both Shapes.

A first inheritance example

Code Block
Java 8 (Update 492)

Run it. The single for loop prints information about three different concrete shapes, even though the loop never says the words Circle or Rectangle. That is polymorphism — the same call (s.describe()) does different things depending on the actual object behind the reference.

How polymorphism actually works

Each object remembers what class it is. When you write s.area(), the JVM looks at the runtime class of s (not the declared type of the variable) and dispatches to that class's area. This is dynamic dispatch.

This is the mechanism that lets new shape classes be added later without changing any of the code in Main. As long as a new class extends Shape and overrides area, the loop works. That property — "code written today keeps working as new types are added tomorrow" — is the chief reason polymorphism exists.

abstract classes and abstract methods

In the example, Shape is abstract. That means:

  • You cannot do new Shape() directly. (What would its area be?)
  • Shape may declare abstract methods (like area()) that have no body and must be implemented by every non-abstract subclass.
  • Shape may also have concrete methods (like describe()) that use the abstract ones.

abstract is the language's way of saying: "this class is incomplete on its own; it exists to be extended."

super and overriding

A subclass can call a superclass method via super.method(...), and can call a superclass constructor via super(...) as the first line of its own constructor.

Code Block
Java 8 (Update 492)

super.describe() says "do whatever the parent class would do, and let me add to it."

The @Override annotation isn't required, but always use it. It asks the compiler to check that you really are overriding a method in a parent class. If you misspell the method name (describ() instead of describe()), the compiler will flag it instead of silently treating it as a new unrelated method.

When inheritance is the wrong tool

Inheritance is a very strong coupling between two classes. The child knows about the parent's fields. The child can be broken by changes to the parent. The child cannot easily change parents later.

A good rule: prefer composition over inheritance. If Car has-a Engine, that is composition (a field of type Engine), not inheritance. Inherit only when the "is-a" relationship is genuine, permanent, and substitutive — meaning anywhere a Shape is expected, a Circle works correctly.

Common red flags that you should not be using inheritance:

  • The subclass overrides almost every method to do something unrelated.
  • The subclass needs to throw "unsupported operation" exceptions because the parent's contract doesn't fit.
  • You wrote extends just to reuse one method.
QuestionSelect one

What does polymorphism let you do?

Convert one type into another at compile time

Allocate less memory for subclasses

Call the same method on different objects and have each respond in its own way, decided at runtime

Write a class without any methods

QuestionSelect one

Which is the best signal that inheritance is appropriate between two classes?

They share a few fields

They sound related in English

Every instance of the child class genuinely is a legitimate instance of the parent and can be used wherever the parent is expected

The child wants to reuse one helper method from the parent

QuestionSelect one

Why is the @Override annotation valuable?

It makes the override happen

It tells the compiler "this method is supposed to override a parent's method," so a typo or signature mismatch becomes a compile error instead of a silently-new method

It marks the method as final

It improves runtime performance

A small polymorphism challenge

Challenge
Java 8 (Update 492)
Speak, animals

Create a class hierarchy:

  • An abstract class Animal with a method String speak() (abstract) and a method String greet() that returns "Hello, I say " + speak().
  • A class Dog extends Animal whose speak() returns "Woof!".
  • A class Cat extends Animal whose speak() returns "Meow.".

Main.java is given and must print exactly:

Hello, I say Woof!
Hello, I say Meow.

Inheritance lets us reuse code along is-a lines. But Java has another tool — interfaces — that captures can-do relationships without the heavy coupling of extends. That is the next page.

On this page