CodeGym /Courses /JAVA 25 SELF /Creating class hierarchies: real-world examples

Creating class hierarchies: real-world examples

JAVA 25 SELF
Level 17, Lesson 3
Available

1. What is a class hierarchy?

In programming (and beyond), a hierarchy is a tree-like structure where we have “general” entities at the top of the tree (base classes) and more specific ones (subclasses) below. In Java, a class hierarchy is built using inheritance: each subclass can itself be a parent for other subclasses, and so on.

Analogy:

  • An animal (Animal) is a general/base class.
  • A mammal (Mammal) is a more specific kind of animal.
  • A dog (Dog) is a more specific kind of mammal.
  • And a specific “Buddy” is an instance of the Dog class.

In code it looks like this:

class Animal { }
class Mammal extends Animal { }
class Dog extends Mammal { }

A class hierarchy lets you describe common properties and behavior “at the top,” and the details “at the bottom.” This makes code more logical and eliminates duplication.

Hierarchy diagram

Animal
├── Mammal
│   ├── Dog
│   └── Cat
└── Bird
    └── Sparrow

2. How to build a hierarchy: logic and practice

Identify the general and the specific

The main rule: the base class should contain what is characteristic of all its descendants. Everything unique should go into subclasses.

Example:

  • All animals can breathe and eat — therefore, the breathe() and eat() methods should be in the Animal class.
  • Only birds can fly — therefore, the fly() method belongs in the Bird class, not in Animal.
  • Only dogs can bark — the bark() method belongs in the Dog class.

Example: Animals

// Base class
class Animal {
    String name;

    Animal(String name) {
        this.name = name;
    }

    void eat() {
        System.out.println(name + " is eating.");
    }

    void makeSound() {
        System.out.println(name + " makes a sound.");
    }
}

// Subclass: Mammal
class Mammal extends Animal {
    Mammal(String name) {
        super(name);
    }

    void feedMilk() {
        System.out.println(name + " feeds its young with milk.");
    }
}

// Subclass: Dog
class Dog extends Mammal {
    Dog(String name) {
        super(name);
    }

    @Override
    void makeSound() {
        System.out.println(name + " barks: Woof-woof!");
    }

    void wagTail() {
        System.out.println(name + " wags its tail.");
    }
}

// Subclass: Cat
class Cat extends Mammal {
    Cat(String name) {
        super(name);
    }

    @Override
    void makeSound() {
        System.out.println(name + " meows: Meow!");
    }

    void purr() {
        System.out.println(name + " purrs.");
    }
}

// Subclass: Bird
class Bird extends Animal {
    Bird(String name) {
        super(name);
    }

    void fly() {
        System.out.println(name + " flies.");
    }

    @Override
    void makeSound() {
        System.out.println(name + " chirps: Chirp-chirp!");
    }
}

Invocation in main:

public class ZooDemo {
    public static void main(String[] args) {
        Dog sharik = new Dog("Buddy");
        Cat murka = new Cat("Kitty");
        Bird sparrow = new Bird("Sparrow");

        sharik.eat();        // Buddy is eating.
        sharik.makeSound();  // Buddy barks: Woof-woof!
        sharik.feedMilk();   // Buddy feeds its young with milk.
        sharik.wagTail();    // Buddy wags its tail.

        murka.eat();         // Kitty is eating.
        murka.makeSound();   // Kitty meows: Meow!
        murka.feedMilk();    // Kitty feeds its young with milk.
        murka.purr();        // Kitty purrs.

        sparrow.eat();       // Sparrow is eating.
        sparrow.makeSound(); // Sparrow chirps: Chirp-chirp!
        sparrow.fly();       // Sparrow flies.
    }
}

Visualization: class tree

Class Parent Features
Animal
Object
name, eat(), makeSound()
Mammal
Animal
feedMilk()
Dog
Mammal
makeSound(), wagTail()
Cat
Mammal
makeSound(), purr()
Bird
Animal
fly(), makeSound()

3. More real-world examples

Geometric shapes

Class hierarchies are great for modeling geometry.

// Base class
class Shape {
    void draw() {
        System.out.println("Drawing a shape.");
    }
}

// Circle
class Circle extends Shape {
    double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    @Override
    void draw() {
        System.out.println("Drawing a circle with radius " + radius);
    }
}

// Rectangle
class Rectangle extends Shape {
    double width, height;

    Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    void draw() {
        System.out.println("Drawing a rectangle " + width + "x" + height);
    }
}

Usage:

public class ShapeDemo {
    public static void main(String[] args) {
        Shape s1 = new Circle(5);
        Shape s2 = new Rectangle(3, 4);

        s1.draw(); // Drawing a circle with radius 5.0
        s2.draw(); // Drawing a rectangle 3.0x4.0
    }
}

Transportation

class Vehicle {
    void move() {
        System.out.println("The vehicle is moving.");
    }
}

class Car extends Vehicle {
    @Override
    void move() {
        System.out.println("The car is driving on the road.");
    }
}

class Bicycle extends Vehicle {
    @Override
    void move() {
        System.out.println("The cyclist is pedaling.");
    }
}

main:

Vehicle v1 = new Car();
Vehicle v2 = new Bicycle();

v1.move(); // The car is driving on the road.
v2.move(); // The cyclist is pedaling.

Users

class User {
    String username;
    User(String username) { this.username = username; }
    void login() { System.out.println(username + " logged in."); }
}

class Admin extends User {
    Admin(String username) { super(username); }
    void deleteUser(String user) {
        System.out.println(username + " deleted user " + user);
    }
}

class Customer extends User {
    Customer(String username) { super(username); }
    void buy() { System.out.println(username + " made a purchase."); }
}

4. Useful tips

Don't overuse inheritance. Inheritance is a tool for “is-a” relationships. If you want to say “A Cat is an Animal,” use inheritance. If “A Cat has a Tail,” use composition (has-a).

Bad:

class Engine { /* ... */ }
class Car extends Engine { /* Is a car an engine? No, this is composition! */ }

Good:

class Car {
    Engine engine; // A car contains an engine
}

Avoid hierarchies that are too deep. The more levels you have, the harder the code is to maintain and understand. Usually, 2–3 levels are the maximum for most tasks.

Avoid hierarchies that are too “flat.” If you have 20 classes all inheriting directly from one base class, you may need to revisit the architecture.

Inheritance “for convenience.” Sometimes you want to “borrow” methods or fields without thinking through the logic. That leads to chaos! If class A and class B are not logically related, do not make class B extends A just for a couple of methods.

Violating the Liskov substitution principle. If a subclass cannot be used in place of its parent without surprises, the hierarchy is designed incorrectly.

Code duplication. If you catch yourself thinking “oh, this method is being copied from class to class,” it may be time to move it to the base class.

5. Common mistakes when building class hierarchies

Error No. 1: Inheritance without an is-a rationale.
If you use inheritance only to access methods or fields, and not because the subclass truly “is” the parent, your code will quickly turn into a mess. For example, “A Car inherits Engine” is not is-a, it is has-a.

Error No. 2: Ignoring a subclass’s unique features.
If all your subclasses look the same and add nothing new, you may not need a hierarchy at all — use a single class.

Error No. 3: Duplicating code in subclasses.
If you copy the same implementation into multiple subclasses, you should move it to the parent class.

Error No. 4: An overly complex or deep hierarchy.
Multi-level hierarchies are hard to maintain and test. The simpler, the better!

Error No. 5: Overriding methods without the @Override annotation.
Without the annotation, it is easy to make a mistake in the method signature, and then the method will not be overridden but treated as new. Always use @Override!

1
Task
JAVA 25 SELF, level 17, lesson 3
Locked
Virtual pet: Bobik eats 🐕
Virtual pet: Bobik eats 🐕
1
Task
JAVA 25 SELF, level 17, lesson 3
Locked
Driving School: Movement and Signals 🚦
Driving School: Movement and Signals 🚦
1
Task
JAVA 25 SELF, level 17, lesson 3
Locked
School Hierarchy: Who Am I? 🎓
School Hierarchy: Who Am I? 🎓
1
Task
JAVA 25 SELF, level 17, lesson 3
Locked
Zoological Encyclopedia: Cat Characteristics 🐈
Zoological Encyclopedia: Cat Characteristics 🐈
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION