rafael_del nero
Java Developer

Java inheritance vs. composition: How to choose

how-to
May 30, 202413 mins
JavaProgramming LanguagesSoftware Development

Compare inheritance and composition, the two fundamental ways to relate Java classes, then practice debugging ClassCastExceptions in Java inheritance.

Comparing apples and oranges.
Credit: christinarosepix/Shutterstock

Inheritance and composition are two programming techniques developers use to establish the relationship between classes and objects. Whereas inheritance derives one class from another, composition defines a class as the sum of its parts.

Classes and objects created through inheritance are tightly coupled because changing the parent or superclass in an inheritance relationship risks breaking your code. Classes and objects created through composition are loosely coupled, meaning that you can more easily change the component parts without breaking your code.

Because loosely coupled code offers more flexibility, many developers believe that composition is a better technique than inheritance, but the truth is more complex. Choosing a programming tool is similar to choosing the correct kitchen tool: You wouldn’t use a butter knife to cut vegetables, and in the same way, you shouldn’t choose composition for every programming scenario. 

Inheritance and composition in Java

In this article, you’ll learn the differences between inheritance and composition and how to decide which is correct for your Java programs:

  • When to use inheritance in Java
  • When to use composition in Java
  • Differences between inheritance and composition
  • Method overriding with Java inheritance
  • Using constructors with inheritance
  • Type casting and the ClassCastException
  • Common mistakes with inheritance in Java
  • What to remember about inheritance in Java

When to use inheritance in Java

In object-oriented programming, we can use inheritance when we know there is an “is a” relationship between a child and its parent class. Some examples would be:

  • A person is a human.
  • A cat is an animal.
  • A car is a vehicle.

In each case, the child or subclass is a specialized version of the parent or superclass. Inheriting from the superclass is an example of code reuse. To better understand this relationship, take a moment to study the Car class, which inherits from Vehicle:


class Vehicle {

    String brand;
    String color;
    double weight;
    double speed;

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

public class Car extends Vehicle {
    String licensePlateNumber;
    String owner;
    String bodyStyle;

    public static void main(String... inheritanceExample) {
        System.out.println(new Vehicle().brand);
        System.out.println(new Car().brand);
        new Car().move();
    }
}

When you are considering using inheritance, ask yourself whether the subclass really is a more specialized version of the superclass. In this case, a car is a type of vehicle, so the inheritance relationship makes sense. 

When to use composition in Java

In object-oriented programming, we can use composition in cases where one object “has” (or is part of) another object. Some examples would be:

  • A car has a battery (a battery is part of a car).
  • A person has a heart  (a heart is part of a person).
  • A house has a living room (a living room is part of a house).

To better understand this type of relationship, consider the composition of a House:


public class CompositionExample {

    public static void main(String... houseComposition) {
        new House(new Bedroom(), new LivingRoom());
        // The house now is composed with a Bedroom and a LivingRoom
    }

    static class House {

        Bedroom bedroom;
        LivingRoom livingRoom;

        House(Bedroom bedroom, LivingRoom livingRoom) {
            this.bedroom = bedroom;
            this.livingRoom = livingRoom;
        }
    }
    static class Bedroom { }
    static class LivingRoom { }
}

In this case, we know that a house has a living room and a bedroom, so we can use the Bedroom and  LivingRoom objects in the composition of a House

Differences between inheritance and composition

Now let’s compare inheritance and composition in Java. Is the following a good example of inheritance?


import java.util.HashSet;

public class CharacterBadExampleInheritance extends HashSet<Object> {

    public static void main(String... badExampleOfInheritance) {
        BadExampleInheritance badExampleInheritance = new BadExampleInheritance();
        badExampleInheritance.add("Homer");
        badExampleInheritance.forEach(System.out::println);
    }

In this case, the answer is no. The child class inherits many methods that it will never use, resulting in tightly coupled code that is confusing and difficult to maintain. If you look closely, it is also clear that this code does not pass the “is a” test.

Now let’s try the same example using composition:


import java.util.HashSet;
import java.util.Set;

public class CharacterCompositionExample {
    static Set<String> set = new HashSet<>();

    public static void main(String... goodExampleOfComposition) {
        set.add("Homer");
        set.forEach(System.out::println);
    }

Using composition for this scenario allows the  CharacterCompositionExample class to use just two of HashSet‘s methods, without inheriting all of them. We end up with simpler, less coupled code that is easier to understand and maintain.

Method overriding with Java inheritance

Inheritance allows us to reuse the methods and other attributes of one class in a new class, which is very convenient. But for inheritance to really work, we also need to be able to change some of the inherited behavior within our new subclass. For instance, we might want to specialize the sound a Cat makes:


class Animal {
    void emitSound() {
        System.out.println("The animal emitted a sound");
    }
}
class Cat extends Animal {
    @Override
    void emitSound() {
        System.out.println("Meow");
    }
}
class Dog extends Animal {
}

public class Main {
    public static void main(String... doYourBest) {
        Animal cat = new Cat(); // Meow
        Animal dog = new Dog(); // The animal emitted a sound
        Animal animal = new Animal(); // The animal emitted a sound
        cat.emitSound();
        dog.emitSound();
        animal.emitSound();
    }
}

This is an example of Java inheritance with method overriding. First, we extend the Animal class to create a new Cat class. Next, we override the Animal class’s emitSound() method to get the specific sound the Cat makes. Even though we’ve declared the class type as Animal, when we instantiate it as Cat we will get the cat’s meow. 

Does Java have multiple inheritance?

Unlike some languages, such as C++, Java does not allow multiple inheritance with classes. You can use multiple inheritance with interfaces, however. The difference between a class and an interface, in this case, is that interfaces don’t keep state.

If you attempt multiple inheritance like I have below, the code won’t compile:


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

A solution using classes would be to inherit one-by-one:


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

Another solution is to replace the classes with interfaces:


interface Animal {}
interface Mammal {}
class Dog implements Animal, Mammal {}

Using ‘super’ to access a parent class’s methods

When two classes are related through inheritance, the child class must be able to access every accessible field, method, or constructor of its parent class. In Java, we use the reserved word super to ensure the child class can still access its parent’s overridden method:


public class SuperWordExample {
    class Character {
        Character() {
            System.out.println("A Character has been created");
        }
        void move() {
            System.out.println("Character walking...");
        }
    }
    class Moe extends Character {
        Moe() {
            super();
        }
        void giveBeer() {
            super.move();
            System.out.println("Give beer");
        }
    }
}

In this example, Character is the parent class for Moe. Using super, we can access Character‘s  move() method to give Moe a beer.

Using constructors with inheritance

When one class inherits from another, the superclass’s constructor will be loaded first, before loading its subclass. In most cases, the reserved word super is added automatically to the constructor. However, if the superclass has a parameter in its constructor, we must deliberately invoke the super constructor, as shown below:


public class ConstructorSuper {
    class Character {
        Character() {
            System.out.println("The super constructor was invoked");
        }
    }
    class Barney extends Character {
        // No need to declare the constructor or to invoke the super constructor
        // The JVM will to that
    }
}

If the parent class has a constructor with at least one parameter, then we must declare the constructor in the subclass and use super to explicitly invoke the parent constructor. The super reserved word won’t be added automatically and the code won’t compile without it. For example:


public class CustomizedConstructorSuper {
    class Character {
        Character(String name) {
            System.out.println(name + "was invoked");
        }
    }
    class Barney extends Character {
        // We will have compilation error if we don't invoke the constructor explicitly
        // We need to add it
        Barney() {
            super("Barney Gumble");
        }
    }
}

Type casting and ClassCastExceptions

Casting is a way of explicitly communicating to the compiler that you really do intend to convert a given type. It’s like saying, “Hey, JVM, I know what I’m doing so please cast this class with this type.” If a class you’ve cast isn’t compatible with the class type you declared, you will get a ClassCastException.

In inheritance, we can assign the child class to the parent class without casting but we can’t assign a parent class to the child class without using casting.

Consider the following example:


public class CastingExample {
    public static void main(String... castingExample) {
        Animal animal = new Animal();
        Dog dogAnimal = (Dog) animal; // We will get ClassCastException
        Dog dog = new Dog();
        Animal dogWithAnimalType = new Dog();
        Dog specificDog = (Dog) dogWithAnimalType;
        specificDog.bark();
        Animal anotherDog = dog; // It's fine here, no need for casting
        System.out.println(((Dog)anotherDog)); // This is another way to cast the object
    }
}
class Animal { }
class Dog extends Animal { void bark() { System.out.println("Au au"); } }

When we try to cast an Animal instance to a Dog we get an exception. This is because the Animal doesn’t know anything about its child. It could be a cat, a bird, a lizard, etc. There is no information about the specific animal. 

The problem in this case is that we’ve instantiated Animal like this:


Animal animal = new Animal();

Then tried to cast it like this:


Dog dogAnimal = (Dog) animal;

Because we don’t have a Dog instance, it’s impossible to assign an Animal to the Dog. If we try, we will get a ClassCastException.

To avoid the exception, we should instantiate the Dog like this:


Dog dog = new Dog();

then assign it to Animal:


Animal anotherDog = dog;

In this case, because  we’ve extended the Animal class, the Dog instance doesn’t even need to be cast; the Animal parent class type simply accepts the assignment.

Casting with supertypes

It’s possible to declare a Dog with the supertype Animal, but if we want to invoke a specific method from Dog, we’ll need to cast it. As an example, what if we wanted to invoke the bark() method?  The Animal supertype has no way to know exactly what animal instance we’re invoking, so we have to cast Dog manually before we can invoke the bark() method:


Animal dogWithAnimalType = new Dog();
Dog specificDog = (Dog) dogWithAnimalType;
specificDog.bark();

You can also use casting without assigning the object to a class type. This approach is handy when you don’t want to declare another variable:


System.out.println(((Dog)anotherDog)); // This is another way to cast the object

Take the Java inheritance challenge!

You’ve learned some important concepts of inheritance, so now it’s time to try out an inheritance challenge. To start, study the following code:


public class InheritanceCompositionChallenge {
    private static int result;
    public static void main(String... doYourBest) {
        Character homer = new Homer();
        homer.drink();
        new Character().drink();
        ((Homer)homer).strangleBart();
        Character character = new Character();
        System.out.println(result);
        ((Homer)character).strangleBart();
    }
    static class Character {
        Character() {
            result++;
        }
        void drink() {
            System.out.println("Drink");
        }
    }
    static class Homer extends Character {
        Lung lung = new Lung();
        void strangleBart() {
            System.out.println("Why you little!");
        }
        void drink() {
            System.out.println("Drink beer");
            lung.damageLungs();
        }
    }
    static class Lung {
        void damageLungs() {
            System.out.println("Soon you will need a transplant");
        }
    }
}

Which of these is the output after running the main method?

A)


Drink
Drink
Why you little!
2
Exception in thread "main" java.lang.ClassCastException:....

B)


Drink beer
Soon you will need a transplant
Drink
Why you little!
Exception in thread "main" java.lang.ClassCastException:....

C)


Drink beer
Soon you will need a transplant
Drink
Why you little!
3
Exception in thread "main" java.lang.ClassCastException:....

D)


Drink beer
Soon you will need a transplant
Drink
Why you little!
2
Why you little!

Solving the Java inheritance challenge

The correct output from the inheritance challenge is C:


Drink beer
Soon you will need a transplant
Drink
Why you little!
3
Exception in thread "main" java.lang.ClassCastException:....

To understand why, simply examine the code, starting from the top:


Character homer = new Homer();
 homer.drink();

Since we instantiate the object with Homer, the Homer method implementation will be executed, producing the following output:


Drink beer
Soon you will need a transplant

Then, we invoke the drink() method directly from the Character class:


new Character().drink();

Which gives us the following output:


Drink beer

At this line, we use casting and invoke the strangleBart() method normally:


 ((Homer)homer).strangleBart();

Then we ask for the resulting output:


System.out.println(result);

Since we know the super constructor always has to be invoked, we just have to count how many times Character or Homer was instantiated. Now we know the output will be 3

Finally, we try to invoke a method from a misused casting:


((Homer) character).strangleBart();

We tried to convert the class type and implementation of Character, so a ClassCastException will be thrown. (This is because there is no Homer information in the Character class.)

Video challenge! Debugging Java inheritance

Debugging is one of the best ways to fully absorb programming concepts while also improving your code. In this video, you can follow along while I debug and explain the Java inheritance challenge.

Common mistakes with Java inheritance

  • Writing inheritance code before thinking it through carefully.
  • Using inheritance when composition is a better option.
  • Not taking advantage of polymorphism when using inheritance.
  • Using inheritance when the “is a” test doesn’t pass.

What to remember about Java inheritance

  • When instantiating a superclass with the subclass, the subclass instantiation will be considered.
  • In order to decide whether to use inheritance, ask yourself whether the child class really is a specialized type of the parent (the “is a” test).
  • The super reserved word will be added automatically in your constructor if you don’t declare it yourself.
  • If you have the parent class type and the instantiation of the subclass for this type, you may cast it to the subclass.
  • If the superclass has a constructor receiving at least one parameter, you must invoke this super constructor in the subclass passing the required parameter.

Learn more about Java inheritance

rafael_del nero
Java Developer

Rafael del Nero is a Java Champion and Oracle Ace, creator of the Java Challengers initiative, and a quiz master in the Oracle Dev Gym. Rafael is the author of "Java Challengers" and "Golden Lessons." He believes there are many techniques involved in creating high-quality software that developers are often unaware of. His purpose is to help Java developers use better programming practices to code quality software for stress-free projects with fewer bugs.

More from this author