DEV Community

Cover image for OOP Series: Inheritance in Java
Suleiman Ibrahim
Suleiman Ibrahim

Posted on • Edited on

OOP Series: Inheritance in Java

INTRODUCTION

Object-oriented Programming (OOP) is a programming paradigm based on the concepts of Objects and Classes. It allows the group or collection of related objects in a class and creates functionalities for use by the class with the help of methods.
Object-oriented programming has four pillars or principles known as:

  • Inheritance
  • Polymorphism
  • Data Abstraction
  • Encapsulation

These four principles define an Object-oriented language. In this article, we will be looking at Inheritance.

CONTENT

  1. Prerequisite knowledge
  2. Inheritance
  3. Direct and Indirect superclass
  4. Composition vs. Inheritance
  5. The Student class
  6. Conclusion

PREREQUISITE KNOWLEDGE

This article explains the concept of inheritance using the Java programming language. Thus, you need the following knowledge of Java:

  • Basic understanding of OOP.
  • Methods.
  • Classes and objects. If you are new to Java or have a rusty knowledge of it, you can do well to check out Hello World Breakdown in Java where we walk through the building blocks of a simple Java program.

INHERITANCE

Inheritance is one of the four pillars of the Object-Oriented Programming paradigm. Inheritance is a concept that allows us to create new classes from an existing class by acquiring or inheriting the properties of that class. And also, adding more functionality to the new class. Take the family hierarchy, for example, the children exhibit some characteristics of either the mother or the father. Some of these characteristics might include:

  • skin color
  • height
  • size
  • ...

Here, we can say that the child inherits those characteristics from the parent. Since those characteristics are present in the parent. Sometimes, the child might inherit not only the characteristics of the parent but the grandparents as well. In other cases, the child might inherit the characteristics of both the parent and grandparent and still display their characteristics, which make the child unique. This is the basic concept of inheritance in OOP. Inheritance allows us to derive a new class from an existing class. The existing class from which we can derive the new classes is called the superclass in Java. It is also known as the base class or parent class in some other languages like C++. While the new class that is derived is called the subclass in Java. It is also known as derived or child class in some other object-oriented languages as well.

Some advantages inheritance provides are:

  • Reusability: It allows the reuse of code written by us or other programmers. Hence, avoiding reinventing the wheel.
  • Flexibility: When we need to update some group of classes, inheritance allows us to update the superclass only. Instead of making the update on all the affected classes.
  • Redundancy: Inheritance allows us to write less code and build upon the previous ones. Thereby reducing redundancy and encouraging cleaner code.
  • Data hiding: The use of private instance variables, and setters and getters methods to access them. This allows restricted access to the variables of a class.

Direct and Indirect Superclass

We can categorize superclasses into two types, direct superclass, and indirect superclass. The direct superclass is the immediate class from which a new class inherits in the class hierarchy. It is the class immediately above the new class in the inheritance hierarchy tree. The indirect superclass is any class in the inheritance hierarchy above the direct superclass. The new class does not directly inherit any attribute from an indirect superclass but gets these attributes from the direct superclass.
All classes in Java directly or indirectly inherit from the Object class, which can be found in java.lang.Object. The Object class provides some functionalities such as equals() (which checks whether two objects are equal), getClass() (which returns the runtime class of a particular object), toString() (which returns a string comprising the name of the class of which the object is an instance and the unsigned hexadecimal representation of the hash code of the object), notify(), notifyAll() and many others which the Object’s subclasses can utilize.

Composition vs. Inheritance

Java only provides support for single inheritance. Unlike other programming languages like C++ that support both single and multiple inheritance. In single inheritance, we can derive a class from only one direct superclass, while in multiple inheritance, we can derive a class from more than one superclass.
The relationship that can exist between two classes can either be is-a relationship or has-a relationship. Is-a relationship implies inheritance, while has-a relationship implies composition.
In inheritance, we can treat an object of a subclass as an object of the superclass. For instance, if a Rectangle class extends the Shape class, then the Rectangle is-a Shape and thus, we can treat it as a Shape. In composition, a class can to have references to objects of other classes as its member. For example, each student has a course of study, so it is necessary to include a reference of the Course class as a member of the Student class. In this case, we can say that each Student has-a a Course.

The Student Class

In this article, we will consider the Student class to explain the concept of inheritance. Below is an image that shows some classes that can we can derive from the Student class and their hierarchical relationship.
Student Hierarchy

Here, we can consider the Student class to be the base class of all classes. Undergraduate and Graduate inherit directly from the Student class. FirstYear and SecondYear inherit directly from the Undergraduate class. In this case, FirstYear and SecondYear have an indirect inheritance to the Student class and a direct inheritance to the Undergraduate class. Since FirstYear and SecondYear inherit directly from the Undergraduate but not directly from the Student class.

package Inheritance;

// Student.java
public class Student {

    private String firstName;
    private String lastName;
    private String studentId;

    public Student(String firstName, String lastName, String studentId) {
        // validate first name
        if (!(firstName.matches("[A-Z][a-zA-Z]+"))) 
            throw new IllegalArgumentException("Invalid first name.");

        // validate last name
        if (!(lastName.matches("[a-zA-Z]+(['-][a-zA-Z]+)*")))
            throw new IllegalArgumentException("Invalid last name");

        // validate student id
        if (studentId.length() == 0)
            throw new IllegalArgumentException("Invalid ID");

        this.firstName = firstName;
        this.lastName = lastName;
        this.studentId = studentId;
    }

    // set and get methods

    public void setFirstName(String firstName) {
        if (!(firstName.matches("[A-Z][a-zA-Z]+"))) 
            throw new IllegalArgumentException("Invalid first name.");

        this.firstName = firstName;        
    }

    public String getFirstName() {
        return firstName;
    }

    public void setLastName(String lastName) {
        if (!(lastName.matches("[a-zA-Z]+(['-][a-zA-Z]+)*")))
            throw new IllegalArgumentException("Invalid last name");

        this.lastName = lastName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setStudentID(String studentId) {
        if (studentId.length() == 0)
            throw new IllegalArgumentException("Invalid ID");

        this.studentId = studentId;
    }

    public String getStudentId() {
        return studentId;
    }

    @Override
    public String toString() {
        return String.format("%s: %s%n%s: %s%n%s: %s%n", 
                "Student ID", getStudentId(), "First Name", getFirstName(),
                "Last Name", getLastName());
    }
}

Enter fullscreen mode Exit fullscreen mode

Student.java first starts by declaring and initializing its instance private instance fields. Recall that any field or member declared as private is only accessible to the class alone, so therefore the instance fields declared in the Student class can’t be accessed outside the class.
This is where the set and get methods come to help. In this case, the set methods first validate the values entered before making them available to the class for use and throw an error with a meaningful message to the user. It means that any class extending the Student class needs not to validate its first name, last name, and student id anymore because the Student class has already handled that. This is one of the advantages of inheritance provides. It prevents programmers from reinventing the wheel. This speeds up production and also reduces the complexity of a program since some implementations are done in the superclass.
The toString() method at the end also shows that the Student class is overriding one of its superclass methods using the @override annotation. Recall that every class in Java by default and directly or indirectly extends the Object superclass, so we don’t have to explicitly extend the Object class at the Student class declaration. This also shows that subclasses can modify or increase the capabilities of the superclass members by simply overriding them in the subclass.

The next program containing the Undergraduate class inherits the properties of the Student class.

package Inheritance;

// Undergraduate.java
public class Undergraduate extends Student{

    private int year;

    public Undergraduate(String firstName, String lastName, String studentId, int year) {
        super(firstName, lastName, studentId);

        this.year = year;
    }

    // set and get methods
    public void setYear(int year) {
        this.year = year;
    }

    public int getYear() {
        return year;
    }

    @Override
    public String toString() {
        return String.format("%sYear: %s%n%s%n", super.toString(), getYear(), "Undergraduate Student");
    }
}

Enter fullscreen mode Exit fullscreen mode

Undegraduate.java first starts at the class declaration by extending/inheriting from the Student class using the extends keyword. extends is a keyword in Java we use to implement inheritance and it can only extend one class since Java only supports single inheritance.
The program continued by declaring an instance field and then declaring the constructor with parameters. Then next to the initializing the superclass variable using the super keyword. A set of parentheses comprising the superclass constructor arguments immediately followed this. super is also a keyword in Java and subclasses use it to initialize the variables of the superclass constructor. Java requires that the first task of any subclass constructor is to call the constructor of its direct superclass.
We don’t need to validate firstName, lastName, and studentId anymore because the superclass has handled that already. And we can access them using the super. followed by their get methods. Similar to the Student class, the Undergraduate class also has its own toString() method which uses super.toString() String representation of the superclass. We can access members of the superclass using the super. followed by the member to access. For example,

super.toString();
Enter fullscreen mode Exit fullscreen mode

The next program containing the Graduate class also inherits from the student class

package Inheritance;

// Graduate.java
public class Graduate extends Student{

    private String program;

    public Graduate(String firstName, String lastName, String studentId, String program) {
        super(firstName, lastName, studentId);

        this.program = program;
    }

    // set and get methods

    public void setProgram(String program) {
        this.program = program;
    }

    public String getProgram() {
        return program;
    }

    @Override
    public String toString() {
        return String.format("%sProgram: %s%n%s%n", 
                super.toString(), getProgram(), "Graduate Student");
    }
}

Enter fullscreen mode Exit fullscreen mode

Similar to the Undergraduate class, the Graduate class also extends the Student class. And contains an instance variable and a constructor that takes in values to initialize the superclass variables. The implementation of the Graduate class is similar to the Undergraduate class. The main purpose of the Graduate class is to show how multiple subclasses can inherit from a single superclass—Student class.
The next program contains the StudentTest class that implements both the Undergraduate and Graduate classes.

package Inheritance;

// StudentTest.java
public class StudentTest {

    public static void main(String[] args) {

        // Declare and initialize objects
        Undergraduate undergraduate = new Undergraduate("Bush", "White", "1021", 3);
        Graduate graduate = new Graduate("Alexa", "Brown", "1102", "Masters");

        // print string representation of objects
        System.out.println(undergraduate);
        System.out.println(graduate);
    }
}

Enter fullscreen mode Exit fullscreen mode

Class StudentTest creates objects of both the Undergraduate and Graduate classes. And then passes the constructor values as well. It then proceeds to print the values of the objects created. Notice that we did not explicitly use the toString() method to print the String representation of the objects. This is because we have already customized them from the Undergraduate and Graduate classes respectively.
Below is the output of executing StudentTest.java

StudentTest.java output

The output first displays the details of the undergraduate student. Followed by the graduate student. Notice that among the undergraduate and graduate student details is an extra detail specifying the type of student. Which was not included in the constructor when created.
These details and the format of the output were all included in the toString() method of the respective subclasses.

Conclusion

Inheritance is a broad topic in Java and other object-oriented programming languages as well. This article implemented the concept of inheritance using the Student class. With Graduate and Undergraduate classes as subclasses. We learned about the concept of superclass and subclass and their usage in inheritance. And then used some keywords such as extends, super as well as using the @override annotation to add functionalities to the default toString method of every class. We then moved to use the setters and getters methods to access the private field of a superclass as well as validate the values before setting them.
In the next article, we will build upon the concept of inheritance by introducing Polymorphism. The ability of objects to exist in many forms. Which is also a core principle of object-oriented programming.

Top comments (0)