1. Inheritance

Inheritance is the process of extending the functionality of a class by defining a new class that inherits the features of the existing class and adds some additional features. Inheritance is used for code reusability and run time polymorphism.

Syntax of extending a class:

class SubClass extends SuperClass{
}

Super-class is the class that is being extended.

Sub-class is the class that extends the super-class.

Note: Only non-static data members and methods of the super-class can be extended by the sub-class.

A super-class can be extended by any number of classes, but a sub-class can only extend a single class, i.e. multiple inheritances are not allowed in java directly. Although it can be achieved by using interfaces.

When a class extends another class then an IS-A relation is created between them.

Take a look at the example below:

Super-class

class Employee{
	
	int salary;
	
	public Employee(int salary){
		this.salary = salary;
	}
}

Sub-class

class Programmer extends Employee{
	
	String name;
	
	public Programmer(String name, int salary){
		super(salary);
		this.name = name;
	}
}

EmployeeTest.java

class EmployeeTest{
	
	public static void main(String arr[]){
		Programmer p = new Programmer("abc", 500000);
		
		System.out.println("Name of the programmer is : "+p.name);
		System.out.println("Salary of the programmer is : "+p.salary);

	}
}

Output:

Name of the programmer is : abc

Salary of the programmer is : 500000

As you can see in the above example that we were able to access the salary property from the Programmer object and also we were able to call the constructor of the super-class using the super(salary); in the constructor of Programmer.java. This all possible because the programmer class extended the employee class so the properties of the employee class were available in the programmer class.

Note: Learn more about the super() keyword in detail here.

2. Code reusability in inheritance

One of the main features of inheritance is the ability to reuse the code of the super-class. Let's understand with the example below:

Rectangle.java

class Rectangle{
	
	int length, breadth;
	
	public Rectangle(int l, int b){
		length = l;
		breadth = b;
	}
	
	public void display(){
		System.out.println("Length : "+length);
		System.out.println("Breadth : "+breadth);
	}

}

Cuboid.java

class Cuboid extends Rectangle{
	
	//additional attribute
	int height;
	
	public Cuboid(int l, int b, int h){
		super(l, b);
		height = h;
	}
	
	public void display(){
		//code reusability
		super.display();
		
		//additional behavior
		System.out.println("Height : "+height);
	}
}

Test.java

class Test{
	public static void main(String arr[]){
		Cuboid c = new Cuboid(9,6,3);
		c.display();
	}
}

Output:

Length : 9
Breadth : 6
Height : 3

In the above example, we reused the code written in display() method of Rectangle.java to print the length and breadth of the cuboid.

3. Runtime polymorphism using inheritance

Runtime polymorphism is the process of resolving a method call at the time of execution, i.e. which method to call is decided while the method is in running state. Runtime polymorphism is achieved with the help of method overriding. Let's first understand what is method overriding.

3.1. Method overriding

When a method that is present in the super-class is also defined by the subclass with the same signature, then it is known as method overriding. Take a look at the below example

Shape.java

class Shape{
	
	public void display(){
		System.out.println("This is a shape");
	}

}

Circle.java

class Circle extends Shape{
	
	public void display(){
		System.out.println("This is a circle");
	}
}

ShapeTest.java

class ShapeTest{

	public static void main(String arr[]){
		Circle c = new Circle();
		c.display();
	}
}

Output:

This is a circle

In this case, the display() method of Circle.java is invoked instead of Shape.java because the display() method was overridden by the Circle.java. If display() were not overridden, then display() of Shape.java would have invoked.

3.2. Implementing runtime polymorphism

Let's see how we can achieve runtime polymorphism using method overriding.

Animal.java

class Animal{
	
	public void makeSound(){
		System.out.println("Animal is making sound");
	}
}

Dog.java

class Dog extends Animal{
	
	public void makeSound(){
		System.out.println("Bow Bow ...");
	}
}

Cat.java

class Cat extends Animal{
	
	public void makeSound(){
		System.out.println("Meow Meow ...");
	}
}

AnimalTest.java

class AnimalTest{
	public static void main(String arr[]){
		Animal animal = new Animal();
		Animal dog = new Dog();
		Animal cat = new Cat();
		doSomething(animal);
		doSomething(dog);
		doSomething(cat);
	}
	
	public static void doSomething(Animal animal){
		animal.makeSound();
	}
	
}

Here you can see we created 3 different types of objects with the same type of reference variable. The method call to makeSound() will be resolved at runtime.

Output:

Animal is making sound
Bow Bow ...
Meow Meow ...

Since doSomething() method can take an argument of type Animal we were able to pass the dog and cat objects to it as they both are of type Animal and because of the concept of method overriding the makeSound() method of cat and dog class was invoked.

This is how you achieve runtime polymorphism. In the doSomething() method you can even pass the objects of classes which will be created sometime in the future if they all fulfill the condition of extending the Animal class.

4. Conclusion

In this tutorial, we saw what is inheritance and how we can use it in java programming to achieve code reusability and runtime polymorphism.