Inheritance in Java

inheritancepolymorphism

In our previous blog we have learned about the JAR Files, its introduction, creating JAR Files, etc. Please visit the blog JAR(Java ARchive) Files for more information. In this blog, we will go through Inheritance, another fundamental concept of Object Oriented Programming n this blog, we will go through the various topics:

Inheritance

The basic idea behind inheritance is that we can create new classes built on existing classes. When we inherit a new class from its existing class then we can reuse its methods and also we can add new methods or fields to it. In general terms, inheritance is nothing but inheriting the properties from its child class. For example, if we take the example of Father and Child, then the child inherits some properties of Father while Father too inherits some properties of his father, so in short the child inherits the properties of his Grandfather as well as of his Father. Inheritance represents is-a relationship that is also called a parent-child relationship.


Why use Inheritance in Java

  • The main purpose behind using inheritance in Java is for achieving runtime polymorphism or commonly known as method overriding and also
  • For method reusability.

Example of Inheritance in Java

...

As shown in the above fig Animal is the parent class and Dog is the child class. It represents is-a relationship between two classes i.e. Dog is an Animal. We will go through the following topics in detail.


Subclass in Java

Subclass is nothing but the child class. Let me give an example. Now suppose we want to define a class named Dog that inherits from class Animal class, then use extend keyword to denote inheritance. The purpose of the extends keyword is it indicates that we are making a class that is derived from its existing class. The existing class is known as the parent class, super class or base class. The new class is known as a child class, subclass, or derived class. The most commonly used terms by developers are superclass and subclass. Now you may be thinking of the Animal class as a superclass because it is superior to its subclass or provides more functionality. But the opposite is true. For example, the Dog class will have more data and more functionality which we will see in the code.

e.g.

class Animal{
  void eat(){
    System.out.println("Inside the eat() method..
");
  }
}

class Dog extends Animal{
  void bark(){
    System.out.println("Inside the bark() method...");
  }
}

class TestingInheritance {
  public static void main(String[] args) {
    Dog d=new Dog();
    d.bark();
    d.eat();
 }
}

Output

Inside the bark() method...
Inside the eat() method...

In the above example, we have used methods such as eat() and bark(). bark() and eat() is called using the Dog object. But if we want the Animal class should call the bark() method then it is not possible because bark() is not present in the Animal class. But that is not the case with the Dog class, it can access both the methods i.e. its own method which is bark() and the method eat() from the Animal class. Even though eat() is not defined explicitly in the Dog class, we can still access it, as it is automatically inherited from its superclass. i.e. Animal class.


Method Overriding in Java

Some of the methods of the superclass are not appropriate for subclasses. Suppose we take the example of the AtrowelEmployee and AtrowelDeveloper class. AtrowelEmployee is a superclass having an instance variable as salary and AtrowelDeveloper class is a subclass having an instance variable as a bonus. getSalary() method is defined in AtrowelEmployee class, but we can override it in the subclass. You may be having question that how getSalary() be implemented in subclass. At first glance, you may be thinking that just to return the sum of salary and bonus field.

e.g.

public class AtrowelDeveloper extends AtrowelEmployee{
. . .
  public double getSalary(){
    return salary + bonus; // it will not work
  }
. . .
}

The above example will throw an error because we cannot directly call access the private fields of the class i.e. AtrowelEmployee class. It means that the getSalary method of AtrowelDeveloper class cannot directly access the field salary. In order to access the private fields of AtrowelEmployee class, the AtrowelDeveloper class has to do what every other method does. i.e. use the public interface, in this case, the public getsalary() method of AtrowelEmployee class. Let's see the example.

e.g.

public double getSalary(){
  double baseSalary = getSalary(); //It will still not work
  return baseSalary + bonus;
}

Now the problem here is that the call to getSalary() will call itself because the AtrowelDeveloper class also has getSalary(), which will lead the program to go in an infinite chain of calls and will crash the program. We will have to mention that we want to call the getSalary() method of the AtrowelEmployee class i.e. superclass not the base class. In order to call the gelSalary() of the superclass we will have to use the super keyword for this purpose:

e.g.

super.getSalary();

The above statement calls the getSalary() method of superclass.

e.g.

public double getSalary(){
  double baseSalary = super.getSalary();
  return baseSalary + bonus;
}

Java Subclass Constructor

Let's complete the above example and add a constructor to it.

e.g.

public AtrowelDeveloper(String name, double salary, int yr, int mon, int day){
  super(name, salary, yr, month, day);
  bonus = 10000;
}

In the above example you may see the super keyword. But here the use of the super keyword is different, it is used here to call the constructor of a superclass having parameters such as name, salary, year, month, and day.

e.g.

super(name, salary, year, month, day);

We have seen above that we cannot directly access the private fields of the superclass in a subclass, so we have initialized them through the constructor. To invoke the constructor using super has the syntax. The call to the constructor must be the first statement in the constructor in the subclass. If the subclass constructor does not call a superclass constructor explicitly, then the no-argument constructor of the superclass is invoked. But if the superclass does not have a no-argument constructor and the subclass constructor also does not call another superclass constructor explicitly, then the Java compiler reports an error. After we have redefined the getSalary() method for AtrowelDeveloper objects, AtrowelDevelopers will automatically have the bonus added to their salaries. Now we will see the example:

e.g.

AtrowelDeveloperTest.java:

public class AtrowelDeveloperTest{
  public static void main(String[] args){
    var boss = new AtrowelDeveloper("John Smith", 80000, 1987, 12, 15);
    boss.setBonus(5000);
    var atrowelstaff = new AtrowelEmployee[3];
    atrowelstaff[0] = boss;
    atrowelstaff[1] = new AtrowelEmployee("Jackson Beth", 50000, 1989, 10, 1);
    atrowelstaff[2] = new AtrowelEmployee("Smith Williams", 30000, 1990, 3, 15);
    for (AtrowelEmployee e : atrowelstaff)
      System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
  }
}

e.g.

AtrowelEmpolyee.java:

import java.time.*;
  public class AtrowelEmployee{
    private String name;
    private double salary;
    private LocalDate hireDay;
    public AtrowelEmployee(String name, double salary, int yr, int mon, int day){
      this.name = name;
      this.salary = salary;
      hireDay = LocalDate.of(yr, mon, day);
    }
    public String getName(){
      return name;
    }
    public double getSalary(){
      return salary;
    }
    public LocalDate getHireDay(){
      return hireDay;
    }
    public void raiseSalary(double byPercent){
      double raiseSal = salary * byPercent / 100;
      salary += raiseSal;
    }
  }

e.g.

AtrowelDeveloper.java:

public class AtrowelDeveloper extends AtrowelEmployee{
    private double bonus;
    public AtrowelDeveloper(String name, double salary, int yr, int mon, int day){
      super(name, salary, yr, mon, day);
      bonus = 0;
    }
    public double getSalary(){
      double baseSalary = super.getSalary();
      return baseSalary + bonus;
    }
    public void setBonus(double b){
      bonus = b;
    }
 }

Output

John Smith 85000.0
Jackson Beth 50000.0
Smith Williams 30000.0

Inheritance Hierarchy

Inheritance does stop at deriving one layer of classes.

...

In the above fig we can see that AtrowelExecutive is the child class or derived class of AtrowelManager and AtrowelManager class, AtrowelSecretary class and AtrowelProgrammer class are all child classes of AtrowelEmployee class. The inheritance hierarchy is a collection of all the classes that extend the common superclass. The inheritance chain describes the path from the particular class to its parent class in the inheritance hierarchy. In fig AtrowelSecretary and AtrowelProgrammer are formed which are derived from AtrowelEmpolyee and there is no relationship between these two classes and AtrowelManager class. Likewise, the process can go on till it is necessary.


Polymorphism

There is a simple rule that helps us to guide whether the inheritance is a good design for the data or not. The rule is the is-a rule which states that every object of the child class or subclass is an object of the parent class or super class. For example, every Dog is an animal, but every animal cannot be a Dog or every Developer is an Employee but every Employee cannot be Developer. The other way of forming a ‘is-a’ rule is can use the child class or subclass object whenever the program expects the parent class or superclass object. For example, we can assign a child class or subclass object to the parent class or superclass variable.

e.g.

AtrowelEmployee atrowelObj;
atrowelObj = new AtrowelEmployee();
atrowelObj = new AtrowelDeveloper();

In Java, object variables are polymorphic. A variable of type AtrowelEmployee can refer to an object of type AtrowelEmployee or to an object of any child class or subclass of the AtrowelEmployee class (such as AtrowelManager, AtrowelExecutive, AtrowelSecretary, and so on).

e.g.

AtrowelManager AtrowelBoss = new AtrowelManager(. . .);
AtrowelEmployee[ ] atrowelStaff = new AtrowelEmployee[3];
atrowelStaff[0] = AtrowelBoss;

In the above snippet, we can see that the atrowelStaff[0] and atrowelBoss refers to the same object. But the atrowelStaff[0] is considered only an AtrowelEmployee object by the compiler which means we can call atrowelBoss.setBonus(10000), but we cannot call atrowelStaff[0].setBonus(10000);

e.g.

atrowelBoss.setBonus(10000);    //The statement is valid
atrowelStaff[0].setBonus(10000); // The statement is not valid

The statement 2 is not a valid statement because the declared type of atrowelStaff[0] is AtrowelEmployee, and the setBonus() method is not a method of the AtrowelEmployee class. Also, we cannot assign the parent class or superclass reference to the child class or subclass variable.

e.g.

AtrowelManager m = staff[i]; //The statement is not a valid statement

The reason is clear that not all AtrowelEmployees are AtrowelManagers.


Understanding Method calls

Understanding how the method called is applied to an object is very important. For example, suppose if we call a.b(args), a is declared as the object of class let's say X. Then what happens is:

  1. The compiler takes a look at the declared type of the object and the method name. Please make a note that there may be multiple methods, all methods having the same name but different parameter types. For example, there may be method b(String) and method b(double). The compiler lists all methods called a in class X and all accessible methods called a in the parent class or superclasses of A. Now the compiler knows all possible applicants for the method to be called.
  2. After that, the compiler determines the types of arguments supplied in the method call. If from all the methods called b and if there is a unique method whose parameter types are the best match for the supplied arguments, then that method is selected to be called. This process is called overloading resolution. For example, if we consider the method call a.b("Welcome to Java Shortkicks"), then the compiler picks b(String) and not b(double). The situation can get complicated because of type conversions (int to double, Developer to Employee, and so on). And suppose if the compiler is unable to find any method with matching parameter types or if all the methods match after applying conversions, the compiler reports an error.
  3. If the method is private, static, final, or a constructor, then the compiler knows which method to call. This is called static binding. Otherwise, the method which is to be called depends on the actual type of the implicit parameter, and dynamic binding must be used at runtime.
  4. When the program runs and uses dynamic binding to call a method, the virtual machine should call the version of the method that is suitable for the actual type of the object to which a refers. Suppose the actual type is Y, a child class or subclass of X and if the class Y defines a method b(String), then that method is called. If that method is not defined in Y, then its(Y’s) parent class or superclass is searched for a method b(String), and so on.

This may be time-consuming to carry out this search every time when a method is called. Rather, the virtual machine precomputes for each class a method table which lists all method signatures and the actual methods to be called. When a method is called actually, then the virtual machine simply makes a table lookup. We will see this through the example which we have seen in the Java Subclass Constructor section. In the example in the method call i.e. e.getSalary() the declared type of e is AtroweEmployee. As we have seen in the code, the AtrowelEmployee class has a method getSalary() with no parameters passed to it. In this case there is no overloading resolution. As the getSalary() is not private, static or final, it is dynamically bound. The virtual machine creates the method tables for the AtrowelEmployee and AtrowelDeveloper classes. The AtrowelEmployee table shows that all methods are defined in the AtrowelEmployee class.

AtrowelEmployee -

getName() - AtrowelEmployee.getName()
getSalary() - AtrowelEmployee.getSalary()
getHireDay() - AtrowelEmployee.getHireDay()
raiseSalary(double) - AtrowelEmployee.raiseSalary(double)

The AtrowelDeveloper table is somewhat different from the AtrowelEmployee table. There are three methods that are inherited i.e. getName(), raiseSalary(), and getHireDay(), one method which is redefined i.e. getSalary(), and one method added i.e. setBounus().

AtrowelDeveloper -

getName() - AtrowelEmployee.getName()
getSalary() - AtrowelManager.getSalary()
getHireDay() - AtrowelEmployee.getHireDay()
raiseSalary(double) - AtrowelEmployee.raiseSalary(double)
setBonus(double) - AtrowelManager.setBonus(double)

At runtime, the call for e.getSalary() is as follows:

  1. Firstly, the virtual machine fetches the method table for the actual type of e. That can be any table i.e.AtrowelEmployee, AtrowelDeveloper, or another subclass of AtrowelEmployee.
  2. Then, the virtual machine looks up the defining class for the getSalary(). After looking into it, it knows which method to call.
  3. Finally, the virtual machine calls the method.

Advantages or Benefits of Inheritance

  1. The important advantage of using Inheritance is code reusability. The derived class can use the methods of its parent class without rewriting it again in the child class.
  2. It saves effort and also time as we don’t have to write the main code again.
  3. It reduces code duplicy, because if we don’t use inheritance, then we will have to write the same code again.
  4. We can also add new features or change the existing features easily in subclasses.i.e. It provides extensibility.
  5. We can also achieve runtime polymorphism or method overriding.
  6. It allows Data Hiding i.e.the base class can decide which data to be kept secure or private so that the derived class(child class) will not be able to modify it.

Disadvantages or Drawbacks of Inheritance

  1. The main disadvantage of Inheritance is that both the base and inherited class, get tightly bound by each other. Because of this Programmers can not use these classes independently of each other i.e it provides no independence.
  2. It decreases the execution speed because the execution of Inheritance takes time and effort.
  3. If the user deletes the superclass (parent class), then the user will have to refactor it if the user has used it and also it will be impacted the child class.

Core Java Tutorial

Get in Touch

Atrowel will be pleased to receive your feedback and suggestions. Those of you who would like to contribute, please send us an email.