Inheritance in Java, Part 1: The extends keyword

how-to
Jun 04, 202411 mins
JavaProgramming LanguagesSoftware Development

Use Java's extends keyword to derive a child class from a parent class, invoke parent class constructors and methods, override methods, and more.

Java supports class reuse through inheritance and composition. This two-part tutorial teaches you how to use inheritance in your Java programs.

What you’ll learn in this Java tutorial

The first half of this introduction to Java inheritance teaches you how to use the extends keyword to derive a child class from a parent class, invoke parent class constructors and methods, and override methods:

  • What is inheritance in Java?
  • Single inheritance and multiple inheritance
  • How to use the extends keyword in Java
  • Understanding Java class hierarchy
  • When to use method overriding vs. method overloading
download
Download the source code for example applications in this tutorial. Created by Jeff Friesen.

What is inheritance in Java?

Inheritance is a programming construct that software developers use to establish is-a relationships between categories. Inheritance enables us to derive more specific categories from more generic ones. The more specific category is a kind of the more generic category. For example, a checking account is a kind of account in which you can make deposits and withdrawals. Similarly, a truck is a kind of vehicle used for hauling large items.

Inheritance can descend through multiple levels, leading to ever-more-specific categories. As an example, Figure 1 shows car and truck inheriting from vehicle; station wagon inheriting from car; and garbage truck inheriting from truck. Arrows point from more specific “child” categories (lower down) to less specific “parent” categories (higher up).

jw inheritance p1 fig1 Jeff Friesen

Figure 1. A pair of inheritance hierarchies are rooted in the common vehicle category

Single inheritance and multiple inheritance

The example in Figure 1 illustrates single inheritance in which a child category inherits state and behaviors from one immediate parent category. In contrast, multiple inheritance enables a child category to inherit state and behaviors from two or more immediate parent categories. The hierarchy in Figure 2 illustrates multiple inheritance.

jw inheritancep1 fig2 Jeff Friesen

Figure 2. Hovercraft multiply inherits from land vehicle and water vehicle categories

Categories are described by classes. Java supports single inheritance through class extension, in which one class directly inherits accessible fields and methods from another class by extending that class. Java doesn’t support multiple inheritance through class extension, however.

When viewing an inheritance hierarchy, you can easily detect multiple inheritance by the presence of a diamond pattern. Figure 2 shows this pattern in the context of vehicle, land vehicle, water vehicle, and hovercraft.

How to use the extends keyword in Java

Java supports class extension via the extends keyword. When present, extends specifies a parent-child relationship between two classes. Below I use extends to establish a relationship between classes Vehicle and Car, and then between Account and SavingsAccount:

Listing 1. The ‘extends’ keyword specifies a parent-child relationship

class Vehicle
{
   // member declarations
}
class Car extends Vehicle
{
   // inherit accessible members from Vehicle
   // provide own member declarations
}
class Account
{
   // member declarations
}
class SavingsAccount extends Account
{
   // inherit accessible members from Account
   // provide own member declarations
}

The extends keyword is specified after the class name and before another class name. The class name before extends identifies the child and the class name after extends identifies the parent. It’s impossible to specify multiple class names after extends because Java doesn’t support class-based multiple inheritance.

These examples codify is-a relationships: Car is a specialized Vehicle and SavingsAccount is a specialized Account. Vehicle and Account are known as base classes, parent classes, or superclasses. Car and SavingsAccount are known as derived classes, child classes, or subclasses.

Child classes inherit accessible fields and methods from their parent classes and other ancestors. They never inherit constructors, however. Instead, child classes declare their own constructors. Furthermore, they can declare their own fields and methods to differentiate them from their parents. Consider Listing 2.

Listing 2. An Account parent class

class Account
{
   private String name;

   private long amount;

   Account(String name, long amount)
   {
      this.name = name;
      setAmount(amount);
   }

   void deposit(long amount)
   {
      this.amount += amount;
   }

   String getName()
   {
      return name;
   }

   long getAmount()
   {
      return amount;
   }

   void setAmount(long amount)
   {
      this.amount = amount;
   }
}

Listing 2 describes a generic bank account class that has a name and an initial amount, which are both set in the constructor. Also, it lets users make deposits. (You can make withdrawals by depositing negative amounts of money but we’ll ignore this possibility.) Note that the account name must be set when an account is created.

Listing 3 presents a SavingsAccount child class that extends its Account parent class.

Listing 3. A SavingsAccount child class extends its Account parent class

class SavingsAccount extends Account
{
   SavingsAccount(long amount)
   {
      super("savings", amount);
   }
}

The SavingsAccount class is trivial because it doesn’t need to declare additional fields or methods. It does, however, declare a constructor that initializes the fields in its Account superclass. Initialization happens when Account‘s constructor is called via Java’s super keyword, followed by a parenthesized argument list.

Listing 4 further extends Account with a CheckingAccount class.

Listing 4. A CheckingAccount child class extends its Account parent class

class CheckingAccount extends Account
{
   CheckingAccount(long amount)
   {
      super("checking", amount);
   }

   void withdraw(long amount)
   {
      setAmount(getAmount() - amount);
   }
}

CheckingAccount is a little more substantial than SavingsAccount because it declares a withdraw() method. Notice this method’s calls to setAmount() and getAmount(), which CheckingAccount inherits from Account. You cannot directly access the amount field in Account because this field is declared private (see Listing 2).

Understanding Java class hierarchy

I’ve created an AccountDemo application class that lets you try out the Account class hierarchy. First, take a look at AccountDemo‘s source code.

Listing 5. AccountDemo demonstrates the account class hierarchy

class AccountDemo
{
   public static void main(String[] args)
   {
      SavingsAccount sa = new SavingsAccount(10000);
      System.out.println("account name: " + sa.getName());
      System.out.println("initial amount: " + sa.getAmount());
      sa.deposit(5000);
      System.out.println("new amount after deposit: " + sa.getAmount());

      CheckingAccount ca = new CheckingAccount(20000);
      System.out.println("account name: " + ca.getName());
      System.out.println("initial amount: " + ca.getAmount());
      ca.deposit(6000);
      System.out.println("new amount after deposit: " + ca.getAmount());
      ca.withdraw(3000);
      System.out.println("new amount after withdrawal: " + ca.getAmount());
   }
}

The main() method in Listing 5 first demonstrates SavingsAccount, then CheckingAccount. Assuming Account.java, SavingsAccount.java, CheckingAccount.java, and AccountDemo.java source files are in the same directory, execute either of the following commands to compile all of these source files:

javac AccountDemo.java
javac *.java

Execute the following command to run the application:

java AccountDemo

You should observe the following output:

account name: savings
initial amount: 10000
new amount after deposit: 15000
account name: checking
initial amount: 20000
new amount after deposit: 26000
new amount after withdrawal: 23000

Method overriding vs. method overloading

A subclass can override (replace) an inherited method so that the subclass’s version of the method is called instead. An overriding method must specify the same name, parameter list, and return type as the method being overridden. To demonstrate, I’ve declared a print() method in the Vehicle class, shown in Listing 6.

Listing 6. Declaring a print() method to be overridden

class Vehicle
{
   private String make;
   private String model;
   private int year;

   Vehicle(String make, String model, int year)
   {
      this.make = make;
      this.model = model;
      this.year = year;
   }

   String getMake()
   {
      return make;
   }

   String getModel()
   {
      return model;
   }

   int getYear()
   {
      return year;
   }

   void print()
   {
      System.out.println("Make: " + make + ", Model: " + model + ", Year: " +
                         year);
   }
}

Next, I override print() in the Truck class.

Listing 7. Overriding print() in a Truck subclass

class Truck extends Vehicle
{
   private double tonnage;

   Truck(String make, String model, int year, double tonnage)
   {
      super(make, model, year);
      this.tonnage = tonnage;
   }

   double getTonnage()
   {
      return tonnage;
   }

   void print()
   {
      super.print();
      System.out.println("Tonnage: " + tonnage);
   }
}

Truck‘s print() method has the same name, return type, and parameter list as Vehicle‘s print() method. Note, too, that Truck‘s print() method first calls Vehicle‘s print() method by prefixing super. to the method name. It’s often a good idea to execute the superclass logic first and then execute the subclass logic.

To complete this example, I’ve excerpted a VehicleDemo class’s main() method:


Truck truck = new Truck("Ford", "F150", 2008, 0.5);
System.out.println("Make = " + truck.getMake());
System.out.println("Model = " + truck.getModel());
System.out.println("Year = " + truck.getYear());
System.out.println("Tonnage = " + truck.getTonnage());
truck.print();

The final line, truck.print();, calls truck‘s print() method. This method first calls Vehicle‘s print() to output the truck’s make, model, and year; then it outputs the truck’s tonnage. This portion of the output is shown below:

Make: Ford, Model: F150, Year: 2008
Tonnage: 0.5

Use @Override to detect method overloading

Suppose you replaced the print() method in Listing 7 with the one below:

void print(String owner)
{
   System.out.print("Owner: " + owner);
   super.print();
}

The modified Truck class now has two print() methods: the preceding explicitly-declared method and the method inherited from Vehicle. The void print(String owner) method doesn’t override Vehicle‘s print() method. Instead, it overloads it.

You can detect an attempt to overload instead of override a method at compile time by prefixing a subclass’s method header with the @Override annotation:

@Override
void print(String owner)
{
   System.out.print("Owner: " + owner);
   super.print();
}

Specifying @Override tells the compiler that the given method overrides another method. If someone attempted to overload the method instead, the compiler would report an error. Without this annotation, the compiler would not report an error because method overloading is legal.

Method overriding and protected methods

Java provides the protected keyword for use in a method-overriding context. You can also use protected for fields. This keyword is commonly used to identify methods that are designed to be overridden, given that not all accessible methods should be overridden.

When you declare a method or field protected, the method or field is accessible to all of the code within any class that has been declared in the same package. It’s also accessible to subclasses regardless of their packages. (I’ll discuss packages in a future article.)

Conclusion

The second half of this introduction to inheritance tours the Object class and its methods. Every Java class inherits from Object, so familiarity with this superclass is fundamental to understanding the Java class hierarchy. Also see my Java tip introducing composition vs. inheritance. Composition offers an important alternative to inheritance for creating relationships between classes. It also happens to solve one of the biggest challenges with inheritance.