Inheritance in Java

Inheritance is a mechanism for deriving a new class from another class (base class). The new class acquires some fields and methods of the base class. Inheritance is one of the main principles of object-oriented programming. It allows developers to build convenient class hierarchies and reuse existing code.

In Java, a class hierarchy is a way of organizing classes in a parent-child relationship, where a child class inherits properties and methods from its parent class, also known as the superclass.

Extending classes

When it comes to inheritance, there are several terms. A class derived from another class is called a subclass (it's also known as a derived classextended class or child class). The class from which the subclass is derived is called a superclass (also a base class or a parent class).

To derive a new class from another, the extends keyword is used. The basic syntax is shown below:

class SuperClass { }

class SubClassA extends SuperClass { }

class SubClassB extends SuperClass { }

class SubClassC extends SubClassA { }

There are some important points regarding inheritance in Java:

  • Java doesn't support multiple-class inheritance, meaning that a class can only inherit from a single superclass;
  • a class hierarchy can have multiple levels (class C can extend class B that extends class A);
  • a superclass can have more than one subclass.

A subclass inherits all public and protected fields and methods from the superclass. A subclass can also add new fields and methods. The inherited and added members will be used in the same way.

A subclass doesn't inherit private fields and methods from the superclass. However, if the superclass has public or protected methods for accessing its private fields, these members can be used inside subclasses.

Constructors are not inherited, but the superclass's constructor can be invoked from the subclass using the special keyword — super.

If you'd like the base class members to be accessible from all subclasses but not from the outside code (excluding the same package), use the access modifier protected.

Inheritance represents the IS-A relationship. A base class represents the general and a subclass represents the particular or specific.

An example of a class hierarchy

Let's consider a more graphic example. A telecommunication company serves clients. It has a small staff consisting only of managers and programmers. Let's consider a class hierarchy for people associated with the company's activities (including clients).

At first, we present the hierarchy as a figure. An arrow indicates that one class extends another one.

The class hierarchy for the telecommunication company

The class hierarchy for the telecommunication company

  • the base class — Person, has fields for storing common data: name, year of birth, and address;
  • the Client class has additional fields to store the contract number and status (gold or not);
  • the Employee class stores the start date of work for the company and the salary;
  • the Programmer class has an array of the programming languages a programmer uses;
  • the Manager class may have a dazzling smile.

Let's see the code:

class Person {
    protected String name;
    protected int yearOfBirth;
    protected String address;

    // public getters and setters for all fields here
}

class Client extends Person {
    protected String contractNumber;
    protected boolean gold;

    // public getters and setters for all fields here
}

class Employee extends Person {
    protected Date startDate;
    protected Long salary;

    // public getters and setters for all fields here
}

class Programmer extends Employee {
    protected String[] programmingLanguages;

    public String[] getProgrammingLanguages() {
        return programmingLanguages;
    }

    public void setProgrammingLanguages(String[] programmingLanguages) {
        this.programmingLanguages = programmingLanguages;
    }
}

class Manager extends Employee {
    protected boolean smile;

    public boolean isSmile() {
        return smile;
    }

    public void setSmile(boolean smile) {
        this.smile = smile;
    }
}

This hierarchy has two levels and five classes overall. All fields are protected, which means they are visible to subclasses. Each class also has public getters and setters, but some are skipped in the code as these classes use the default no-argument constructor.

Let's create an object of the Programmer class and fill the inherited fields using the inherited setters. To read the values of the fields, we can use inherited getters.

Programmer p = new Programmer();

p.setName("John Elephant");
p.setYearOfBirth(1985);
p.setAddress("Some street, 15");
p.setStartDate(new Date());
p.setSalary(500_000L);
p.setProgrammingLanguages(new String[] { "Java", "Scala", "Kotlin" });

System.out.println(p.getName()); // John Elephant
System.out.println(p.getSalary()); // 500000
System.out.println(Arrays.toString(p.getProgrammingLanguages())); // [Java, Scala, Kotlin]

We can also create an instance of any class included in the considered hierarchy.

So, inheritance provides a powerful mechanism for code reuse and writing convenient hierarchies, which is a key feature of object-oriented programming. Many things in the real world can be simulated, like hierarchies from a more general to a more particular concept.

Final classes

If a class is declared with the keyword final, it cannot have subclasses at all.

final class SuperClass { }

If you try to extend the class, a compile-time error will occur.

Some standard classes are declared as final: IntegerLongStringMath. They cannot be extended from.

Inheritance allows you to build class hierarchies where subclasses (children) take some fields and methods of the superclass (parent). Such a hierarchy can have multiple levels, but every class can inherit only from a single superclass. A good class hierarchy helps to avoid code duplication and makes your program more modular. If a class should not have subclasses, it should be marked as final. One of the types of polymorphism is based on inheritance and method overriding.

Keyword "super"

Sometimes when we define a new subclass we need to access members or constructors of its superclass. Java provides a special keyword called super to do this. This keyword can be used in several cases:

  • to access instance fields of the parent class;
  • to invoke methods of the parent class;
  • to invoke constructors of the parent class (no-arg or parameterized).

Let's consider all of these cases with examples.

Accessing superclass fields and methods

The super keyword can be used to access instance methods or fields of the superclass. In a sense, it is similar to the this keyword, but it refers to the immediate parent class object.

The super keyword is optional if members of a subclass have different names from members of the superclass. Otherwise, using super is the right way to access hidden (with the same name) members of the base class.

Example. There are two classes: SuperClass and SubClass. Each class has a field and a method.

class SuperClass {
    
    protected int field;

    protected int getField() {
        return field;
    }
    
    protected void printBaseValue() {
        System.out.println(field);
    }
}

class SubClass extends SuperClass {
    
    protected int field;

    public SubClass() {
        this.field = 30;  // It initializes the field of SubClass
        field = 30;       // It also initializes the field of SubClass
        super.field = 20; // It initializes the field of SuperClass
    }

    /**     
     * It prints the value of SuperClass and then the value of SubClass
     */
    public void printSubValue() {
        super.printBaseValue(); // It invokes the method of SuperClass, super is optional here
        System.out.println(field);
    }
}

In the constructor of SubClass, the superclass field is initialized using the super keyword. We need to use the keyword here because the subclass field hides the base class field with the same name.

In the body of the method printSubValue, the superclass method printBaseValue is invoked. Here, the super keyword is optional. It is required when a subclass method has the same name as a method in the base class. This case will be considered in the topic concerning overriding.

Invoking superclass constructor

Constructors are not inherited by subclasses, but a superclass constructor can be invoked from a subclass using the super keyword with parentheses. We can also pass some arguments to the superclass constructor.

Two important points:

  • Invoking super(...) must be the first statement in a subclass constructor, otherwise, the code cannot be compiled;
  • The default constructor of a subclass automatically calls the no-argument constructor of the superclass.

Example. Here are two classes Person and Employee. The second class extends the first one. Each class has a constructor to initialize fields.

class Person {

    protected String name;
    protected int yearOfBirth;
    protected String address;

    public Person(String name, int yearOfBirth, String address) {
        this.name = name;
        this.yearOfBirth = yearOfBirth;
        this.address = address;
    }

    // getters and setters
}

class Employee extends Person {

    protected Date startDate;
    protected Long salary;

    public Employee(String name, int yearOfBirth, String address, Date startDate, Long salary) {
        super(name, yearOfBirth, address); // invoking a constructor of the superclass
        
        this.startDate = startDate;
        this.salary = salary;
    }

    // getters and setters
}

In the provided example, the constructor of the Employee class invokes the parent class constructor to assign values to the passed fields. In a way, it resembles working with multiple constructors using this().

Create a free account to access the full topic

“It has all the necessary theory, lots of practice, and projects of different levels. I haven't skipped any of the 3000+ coding exercises.”
Andrei Maftei
Hyperskill Graduate