Inheritance in Java

Inheritance in Java is a mechanism by which one class can inherit the properties and behaviors(fields and methods) of another class. It is an integral concept of object oriented programming.

The concept of inheritance in Java allows us to create new classes by reusing the features of existing class. You can also add additional fields and methods in to your new class to add some additional features on top of what is inherited from existing class.

The new class which inherits the properties from an existing class is known as subclass(child or derived class) whereas the existing class whose properties are inherited is known as superclass (parent or base class).

Keyword "extends" is used to inherit the properties of a class. Here is the syntax of extends keyword.

class A {
...
...
}
class B extends A {
...
...
}

In the above syntax, the class B is created by inheriting the methods and fields from the class A. Hence, class B is a subclass and class A is a superclass.


Example java program for inheritance

class Player {
  String name;

  Player(String name) {
    this.name = name;
  }
  public void run() {
    System.out.println(name + " is running");
  }
}
// SoccerPlayer class inherits features from Player
class SoccerPlayer extends Player {
  SoccerPlayer(String name) {
    super(name);
  }
  public void kick() {
    System.out.println(name + " is kicking the ball");
  }
}

public class InheritanceExample {
  public static void main(String[] args) {
    SoccerPlayer soccerPlayer = new SoccerPlayer("John");
    // Calling base class method
    soccerPlayer.run();
    // Calling subclass method
    soccerPlayer.kick();
  }
}
Output
John is running
John is kicking the ball

In the above program, we have created a subclass SoccerPlayer which inherits from superclass Player. Since SoccerPlayer class inherits from Player class, an object of SoccerPlayer class can access all non-private methods and fields of Player class.

Inside main method of InheritanceExample1 class, an onject of SoccerPlayer is able to call superclass method run() as well as subclass method kick().


Why we need Inheritance ?

Inheritance represents the IS-A relationship which is also known as a parent-child relationship. We use inheritance only if there exists an is-a relationship between two classes. For example, Dolphin is a Fish, Mango is a Fruit, Red is a color etc. Here, Dolphin can inherit from Fish, Mango can inherit from Fruit and so on.

With inheritance we can create a super class with basic features and create its specialized versions by creating sub classes that inherit from super class.

Let us consider a smartphone company, who manufactures multiple smartphones. Although, smartphone models have different specific features like wireless charging, dual sim etc all models include some common components and features like touch screen and microphone.

It makes sense to create a basic design of a smartphone and extend it to create specialized models, rather than designing each new smartphone model from scratch everytime.


Advantages of Inheritance

  • Code Reusability : Inheritance allows the reuse of code from existing classes, reducing redundancy and promoting a more efficient and maintainable codebase.

  • Logical Hierarchy : Inheritance facilitates the creation of a logical hierarchy of classes, modeling relationships between different entities in a system and improving code organization.

  • Polymorphism : Inheritance supports polymorphism, enabling objects of the derived class to be treated as objects of the base class. This fosters flexibility and extensibility in the code.

  • Method Overriding : Method overriding allows customization of behavior in subclasses, providing a way to adapt or extend functionality inherited from the superclass.

Types of inheritance in java

On the basis of class, there are three types of inheritance in java single, multilevel and hierarchical inheritance. Multiple and hybrid inheritance is not supported in java through class.

Single Inheritance

When a subclass inherit from one superclass it is known as Single Inheritance. Here is an example of a single inheritance. In the below image, subclass Dolphin inherits from a single superclass Fish.

Java Single Inheritance
class Fish {
  public void swim() {
    System.out.println("Fish is swimming");
  }
}
// Dolphin class inherits from Fish
class Dolphin extends Fish {
  public void jump() {
    System.out.println("Dolphin is jumping");
  }
}

public class SingleInheritance {
  public static void main(String[] args) {
   Dolphin dol = new Dolphin();
    // Calling base class method
    dol.swim();
    // Calling subclass method
    dol.jump();
  }
}
Output
Fish is swimming
Dolphin is jumping

In above example, Dolphin class inherits from one superclass Fish. This is an example of single inheritance.


Multilevel Inheritance

When there is a chain of inheritance, it is known as multilevel inheritance. For Example, class "Dolphin" inherits from class "Fish" and then class "River Dolphin" inherits from "Dolphin", so there is a inheritance chain.

Java Multilevel Inheritance
class Fish {
  public void swim() {
    System.out.println("Fish is swimming");
  }
}
// Dolphin class inherits from Fish
class Dolphin extends Fish {
  public void jump() {
    System.out.println("Dolphin is jumping");
  }
}
// River Dolphin class inherits from Dolphin
class RiverDolphin extends Dolphin {
  public void eat() {
    System.out.println("River Dolphin is eating");
  }
}

public class MultilevelInheritance {
  public static void main(String[] args) {
    RiverDolphin rd = new RiverDolphin();
    rd.swim();
    rd.jump();
    rd.eat();
  }
}
Output
Fish is swimming
Dolphin is jumping
River Dolphin is eating

In above example, RiverDolphin class inherits from one superclass Dolphin and at the same time class Dolphin inherits from base class Fish. An object of RiverDolphin can access non-private methods of all of it's super classes(Fish and Dolphin). Simularly, an object of Dolphin class can access non-private methods of Fish.


Hierarchical Inheritance

When two or more subclasses inherits a single superclass, it is known as hierarchical inheritance. In the below image, both Dolphin and Shark classes inherits the Fish class, so there is hierarchical inheritance.

Java Hierarchical Inheritance
class Fish {
  public void swim() {
    System.out.println("Fish is swimming");
  }
}
// Dolphin class inherits from Fish
class Dolphin extends Fish {
  public void jump() {
    System.out.println("Dolphin is jumping");
  }
}
// Shark class inherits from Fish
class Shark extends Fish {
  public void attack() {
    System.out.println("Shark is attacking");
  }
}
public class HierarchicalInheritance {
  public static void main(String[] args) {
    Shark sh = new Shark();
    Dolphin dol = new Dolphin();
    // Calling base class method
    sh.swim();
    dol.swim();
    // Calling subclass specific methods
    sh.attack();
    dol.jump();
  }
}
Output
Fish is swimming
Fish is swimming
Shark is attacking
Dolphin is jumping

In above example, both Dolphin and Shark class inherits from one superclass Fish. An object of subclasses Dolphin and Shark cann access non-private methods and fields of base class Fish.


Protected Access Modifier

Protected member variables, methods and constructors can only be accessed from it's own class, classes of same package and subclasses in other packages. Outside of it's own package, the scope of protected members are limited to only subclasses.

  • Keyword "protected" is used for protected access modifiers.

  • Class and interfaces cannot be protected.

  • Methods and fields of interfaces cannot be declared as protected.

  • Unlike public access modifier, protected have limited visibility outside of it's package. Protected variables and methods are used when we want to restrict it's access from all classes outside of my package except sub-classes.

Here is an example of protected access to a member method fly using protected keyword.

class Bird {
  protected void fly() {
    System.out.println("Bird is flying");
  }
}

public class Eagle extends Bird {
  public static void main(String[] args) {
    // Creating an object of Eagle
    Eagle eagle1 = new Eagle();
    // Accessing protected method
    eagle1.fly();
  }
}
Output
Bird is flying

In above program, we have declared a class called "Bird" which contans a protected method fly. Subclass Eagle inherits from class Bird. We created an object of class Eagle called "eagle1". Because fly method of Bird class is protected, subclass Eagle can access it using dot opertaor from it's main method.


Super Keyword in Java

super is a keyword in java which is used in subclass to refers to the immediate super class object. Unlike "this" reference which refers to current object, super refers to immediate super(parent) class object.

We can use super() in the constructor of subclass to call the constructor of parent class. It's a special form of the super keyword. We can call parameterized constructor of parent class by passing appropriate argument while calling super class constructor.
To learn more about super keywprd, visit Java super keyword.


Best Practices of Using Inheritance in Java

While inheritance is a powerful tool in Java, it should be used judiciously to ensure that the code remains maintainable, extensible, and adheres to good design principles. Here are some best practices and considerations to keep in mind when working with inheritance in Java:
  • Favor Composition over Inheritance : Composition, where a class contains an instance of another class rather than inheriting from it, is often preferable over inheritance. This promotes code reuse without introducing the complexities associated with class hierarchies. It also adheres more closely to the principles of encapsulation and flexibility.

  • Follow the "IS-A" Relationship : Inheritance should be used when a "IS-A" relationship exists between the subclass and the superclass. For example, if a subclass can be considered a specialized form of the superclass, then inheritance is appropriate. Avoid forcing inheritance solely for code reuse without a meaningful relationship.

  • Avoid Deep Inheritance Hierarchies : Deep inheritance hierarchies, where there are many levels of subclasses, can make the code harder to understand and maintain. Strive for a balanced hierarchy, keeping it shallow and focused on representing meaningful relationships.

  • Prefer Composition for Code Reuse : Composition often provides a more flexible and maintainable approach for code reuse. By composing classes with other classes or interfaces, you can achieve reusability without introducing the tight coupling associated with inheritance.

  • Use Abstract Classes and Interfaces Wisely : Abstract classes and interfaces are essential in promoting polymorphism and defining contracts. Abstract classes are suitable for providing a common base for subclasses, while interfaces are useful for defining shared behaviors. Be cautious not to create deep hierarchies with too many abstract classes, as this can lead to complexity and maintenance challenges.