Inheritance: An Idea of Code Reusability in OOPS

Inheritance is one of the core principles of object-oriented programming (OOP), which help us derive a class from another class or a hierarchy of classes that share a set of attributes and methods. It is a relationship between a superclass (a generalised class) and a subclass (a specialised class), where subclass inherits data and behavior from superclass.

  • Inheritance represents the IS-A relationship.
  • Class derived from another class is called subclass (derived class or child class), and a class from which subclass is derived is called superclass (base class or parent class).

Inheritance example in oops

How to use inheritance in OOPS?

We use keyword extends to implement inheritance in Java.

class Superclass
{
    //methods and attributes
}

class Subclass extends Superclass
{
    //methods and attributes
}
  • The inherited attributes or methods in the subclass can be used directly, just like any other attributes or methods.
  • We can declare new attributes or methods in the subclass that are not in the superclass.
  • We can write a new method in the subclass with the same signature as the one in the superclass. This is called method overriding.
  • We can also declare an attribute in the subclass with the same name as the one in the superclass, thus hiding it (This is not recommended).

Inheritance code example 1

class Calculator { 
    int c; 
    public void add(int a, int b) {
        c = a + b; 
        System.out.println("Sum:" + c); 
    } 
  
    public void subtract(int a, int b) { 
        c = a - b; 
        System.out.println("Subtraction:" + c); 
    } 
} 
 
public class AdvancedCalculator extends Calculator { 
    public void multiplication(int a, int b) { 
        c = a * b; 
        System.out.println("Multiplication:" + c); 
    } 
   
    public void division(int a , int b){
        c = a / b;
        System.out.println("division:" + c); 
    }
} 
  
public class CalculatorDemo {
    public static void main(String args[]) { 
        int a = 5, b = 4; 
        AdvancedCalculator Cal = new AdvancedCalculator(); 
        Cal.add(a, b); 
        Cal.subtract(a, b); 
        Cal.multiplication(a, b); 
        Cal.division(a, b);
    } 
}

In the above program, when an object of AdvancedCalculator class is created, a copy of all methods and fields of the superclass Calculator acquire memory in this object. So by using an object of a subclass we can also access the members of a superclass.

Shared object memory in inheritance

When to use Inheritance in OOPS?

When some classes are closely related, we can identify common attributes and methods and add them to a superclass. After this, we use inheritance to define subclasses and specialise them with capabilities beyond those inherited from the superclass.

In another scenario, memory and processing resources might be wasted if subclasses are larger than they need to be (i.e., contain too much functionality). So we can extend the superclass that includes the functionality closest to what is required.

Advantages of Inheritance

Code reusability: One of the major purposes of inheritance is code reusability. Suppose there is an existing class A, and we want to create a class B that already includes some part of the code of class A. So instead of writing the same logic inside class B, we can derive class B from class A to reuse the data and methods of class A.

Avoid code duplication: One of the key benefits of inheritance is to minimize the amount of duplicate code by sharing common code among several subclasses. When similar code exists in two related classes, we can move the common code up to a mutual superclass.

Improve code flexibility and extensibility: The best thing is that any future changes made to the attributes and methods of a superclass will be automatically applied to the derived class. In other words, using inheritance, common attributes and methods of all classes in the hierarchy are declared in a superclass. When changes are required, we only need to make the changes in the superclass and subclasses will inherit those changes. If required, the subclass can also add new attributes add new methods.

Provide better code structure and management: Inheritance also makes the sub-classes follow a standard interface. It provides a clear code structure that is easy to understand because classes become grouped together in a hierarchical tree structure.

Help to achieve run time polymorphism: Inheritance provides the capability of a subclass to override a superclass method by providing a new implementation.

Avoid possible code errors: Without inheritance, we need to make changes to all the existing source code files that contain the same logic. Copying and pasting code from one class to another may spread errors across multiple source code files. So inheritance helps us to avoid possible errors as well.

Preserves the integrity of the superclass: Declaring a subclass does not affect its superclass’s source code. So inheritance preserves the integrity of superclass. 

Data hiding: The base class can be set to keep some data private so that it cannot be altered by the derived class. This is an example of encapsulation, where access to data is restricted to only classes that need it for their role.

Disadvantages of Inheritance

Tight coupling: Parent and child class can get tightly coupled and both cannot be used independently. The idea is simple: When we inherit something from a parent class, we inherit every public or protected declaration, whether we need it or not. A change in the parent class will affect all child classes.

Slow performance of inherited methods: Inherited methods work slower than normal class methods due to several levels of indirection. It takes program to jump through all levels of inherited classes. If a given class has five levels of hierarchy above it, it will take five jumps to run through a function defined in each of those classes.

Extra maintenance effort on code change: Adding new features during maintenance, we need to change both parent and child classes. If a method is removed from the parent class, we need to re-factor code in case of using that method. Here things can get a bit complicated. The idea is simple: Improper use of inheritance can lead to wrong solutions!

Method Overriding in Inheritance

We can override superclass methods so that meaningful implementation of superclass method can be defined in subclass. This is also known as runtime polymorphism. In other words, method overriding helps us to implement the idea of polymorphism, which allows different classes to have unique implementations for the same method.

If there is a requirement to override a method:

  • We can write a new method in subclass by using the same name, same access modifier, same parameters, and same return type as in the superclass.
  • The method in the derived class or classes must have a different implementation.

    class Superclass {
      // Other methods and attributes
      ......
      void someMethod() {
          //original implementation
      }
    }
    
    class Subclass extends Superclass {
      // Other methods and attributes
      ......
      @override
      void someMethod() {
      //new implementation
      }
    }

Typecasting in Java

Typecasting is a process to reference a subclass as an instance of its superclass i.e. treating the subclass as if it were of superclass type. This is a good way to create a modular code as we can write code that will work for any subclass of the same superclass. There are two types of typecasting:

Upcasting: We can create an instance of a subclass and then assign it to a superclass variable, this is called upcasting.

Dog dog = new Dog();
Animal animal = dog;
// It is okay becasue Dog is also an Animal

Downcasting: When an instance of a superclass is assigned to a subclass object, then it’s called downcasting. We need to explicitly cast this to subclass type.

Dog dog1 = new Dog();
Animal animal = dog1;

// downcast to dog again
Dog dog2 = (Dog) animal;

Some important notes

  • We can upcast any subclass to its superclass but only those objects can be downcast that were originally of the subclass type.
  • The compiler will throw an ClassCastException error at runtime when we try to perform the wrong typecasting. Below are some of the cases:
//ClassCastException case 1
Dog dog = new Dog();
Animal animal = dog;
Lion lion = (Lion) animal;

//ClassCastException case 2
Animal animal = new Animal
Lion lion = (Lion) animal;
  • The upcast object still retains the fields it had and therefore can be added back to make it a valid object of the child class type again.

Constructors in Inheritance

When we instantiate a subclass object, chain of constructor calls happens: The subclass constructor first invokes superclass constructor, and the last constructor call completed in the chain is the subclass constructor.

Constructors in inheritance visualization

A compilation error occurs if a subclass constructor calls one of its superclass constructors with arguments that do not match exactly the number and types of parameters specified in one of the superclass constructor declarations.

Inheriting constructors: A subclass inherits all members (fields, methods, and nested classes) from its superclass. Constructors are not members, so they are not inherited by subclasses. But the constructor of superclass can be invoked from the subclass either implicitly or by using the keyword super.

super keyword in Java

The super keyword allows child class to access features from parent class regardless of their value in child class. In other words, subclasses can define new local methods or attribute to use or they can use the super keyword to call inherited methods or attributes or the superclass constructor. 

So the super keyword is used for three purposes:

  • Access superclass attributes: super.variablename allows the subclass to access the value of variablename set in the superclass.
  • Calling a superclass method: super.method() allow the subclass to access the superclass implementation of the method(). This is only required if child class also has a method with the same name.
  • Using constructors: This allows us to create new instances of superclass from within a subclass. Calling the super constructor creates a new object that requires all the fields defined in the superclass constructor.
public Dog(String color, String owner){
    super(color); //parent class constructor
    this.owner = owner;
}

Note: When superclass method is overridden in subclass, sometimes subclass version often calls the superclass version to do a portion of the work. Failure to use the keyword super with the superclass method name when referencing the superclass method causes the subclass method to call itself, creating an error called infinite recursion!

Inheritance and access modifiers in java

As we have seen during encapsulation, access modifiers help us implement an information-hiding mechanism. They can also affect access to data and methods within inheritance hierarchy.

  • Private attributes or methods can only be accessed within the same class.
  • Methods and attributes without access modifier can be accessed within the same class and all other classes within the same package.
  • Protected methods and attributes can be accessed within the same class, by all subclasses, and by all classes within the same package.
  • All classes can access public attributes and methods.

Access to data and methods using inheritance and access modifiers in java

Inheritance java code: Example 2

Animal class code

public class Animal {
    private String color;
    public Animal(){}
    public Animal(String color){
        this.color = color;
    }
    public boolean getColor() {
        return color;
    }
    public void setColor(String color) {
    this.color = color;
    }
    // method in the superclass
    public void eat() {
        System.out.println("I can eat");
    }
}

Dog class code: Inheriting Animal class

public class Dog extends Animal {
    private String owner;
    public Dog(String color, String owner){
        super(color); //parent class constructor
        this.owner = owner;
    }
    public String getOwner() {
        return owner;
    }
    public void setOwner(String owner) {
        this.owner = owner;
    }
    // overriding the eat() method
    @Override
    public void eat() {
        System.out.println("Eat both bread and meat");
    }
    
    // new method in subclass
    public void bark() {
        System.out.println("A dog can bark");
    }
}

Lion class code: Inheriting Animal class

public class Lion extends Animal {
    private String jungleName;
    public Lion(String color, String jungleName){
        super(color);//parent class constructor
        this.jungleName = jungleName;
    }
    public String getJungle() {
        return jungleName;
    }
    public void setJungle(String jungleName) {
        this.jungleName = jungleName;
    }
    // overriding the eat() method
    @Override
    public void eat() {
        System.out.println("Only eat meat");
    }
    
    // new method in subclass
    public void roar() {
        System.out.println("A lion can roar");
    }
}

Demo code for inheritance

public class AnimalInheritance {
    public static void main(String[] args) {
        Dog dog = new Dog("Black", "Shubham Gautam");
        System.out.println("Dog color" + dog.getColor());
        System.out.println("What dog eat? " + dog.eat());
        
        Lion lion = new Lion("Brown", "Africa");
        System.out.println("Lion color" + lion.getColor());
        System.out.println("What lion eat? " + lion.eat());
       
        //upcasting
        Animal animal = dog;
        System.out.println("Dog sound " + animal.bark());
        System.out.println("Dog owner " + animal.getOwner());
        
        animal = lion;
        System.out.println("Lion sound? " + lion.roar());
        System.out.println("Lion Jungle Name " + lion.getJungle());
    }
}

Types of Inheritance in Java

Single Inheritance: Subclasses inherit characteristics from a single superclass.

Multilevel Inheritance: A subclass may have its own subclasses. In other words, a subclass of a superclass can itself be a superclass to other subclasses.

Hierarchical Inheritance: A base class acts as the parent superclass to multiple levels of subclasses.

Hybrid Inheritance: A combination of one or more of the other inheritance types. Mostly, it is a situation of single and multiple inheritances. In Java, hybrid inheritance is also not possible with classes, but it can be achieved through Interfaces.

Multiple Inheritance: A subclass may have more than one superclass and inherit characteristics from all of them. Java does not support multiple inheritance with classes, but it can be achieved through Interfaces.

Types of inheritance in java

Default superclass: Except Object class, which has no superclass, every class has one and only one direct superclass (single inheritance). In the absence of any other explicit superclass, every class is implicitly a subclass of the Object class.

Important facts about inheritance in Java

  • Declaring superclass attributes private enables the superclass implementation to change these attributes without affecting subclass implementations. 
  • Methods of a subclass cannot directly access private members of their superclass. A subclass can only change the state of private superclass attributes through non-private getters and setters methods provided in the superclass. When possible, do not include protected attributes in a superclass. Instead, we can include non-private getters and setters methods that access private attributes.
  • A public method of the superclass cannot become a protected or private method in the subclass. Similarly, a protected method of the superclass cannot become a private method in the subclass. Doing this would break the IS-A relationship because it is required that all subclass objects be able to respond to method calls that are declared public in the superclass.
  • Although inheriting from a class does not require access to the class source code. If needed, we should go through its source code to understand how the class is implemented. The idea is simple: We need to ensure that we extend a class that performs well and is securely implemented.
  • If superclass doesn’t have a default constructor, the subclass also needs to have an explicit constructor defined. Else it will throw compile time exception. In the subclass constructor, a call to the superclass constructor is mandatory in this case and it should be the first statement in the subclass constructor.
  • The compiler will know that we are overriding a method and if something changes in the superclass method, we will get a compile-time error rather than getting unwanted results at the runtime.

Critical ideas to think about!

  • Why multiple and hybrid Inheritance are not supported in Java?
  • How do we use the final keyword in inheritance?
  • Which SOLID principle uses the idea of Inheritance?

Please share your feedback and insights. Enjoy learning, Enjoy oops!

Share feedback with us

More blogs to explore

Our weekly newsletter

Subscribe to get weekly content on data structure and algorithms, machine learning, system design and oops.

© 2022 Code Algorithms Pvt. Ltd.

All rights reserved.