Inheritance and Polymorphism in Java: A Complete Guide
Table of contents
- What is Inheritance?
- But Why do we need Inheritance?
- What if we want to execute the all methods of Class A in Class B?
- Types of Inheritance in Java :
- Polymorphism in Java:
- What is Polymorphism and why do we need it?
- Types of Polymorphism in Java:
- Some Common mistakes while working with Inheritance.
- Solutions for the mistake
- Summary:
What is Inheritance?
Inheritance is nothing but acquiring the properties and behavior of a Parent class to the child class. Here we create a new class from the existing class using the extends
keyword. The new class will be called a Sub-Class or Child Class whereas the existing class will be called a Parent Class. Now by default the child class will acquire the entire behavior and properties of the Parent Class.
But Why do we need Inheritance?
Let's understand this with one example code
class A{
void m1(){
System.out.println("Hello World");
}
}
class B{
void m2(){
System.out.println("Hello Java");
}
}
public class Main
{
public static void main(String[] args) {
//Object creation of A Class
A a = new A();
a.m1();
//Object creation of B Class
B b = new B();
b.m2();
}
}
Output:
Hello World
Hello Java
Here in the above example code, we have Class A
with the method m1()
and Class B
with the method m2()
. Now to execute/call both methods in Main Class
, we have created 2 objects of each class.
What if we want to execute the all methods of Class A in Class B?
One approach is to rewrite all of Class A's methods in Class B, and then create an object of Class B to execute them. However, this would be impractical if Class A contains a large number of methods.
A better approach is to create an object of Class A in Class B's main method. This way, you can execute the methods of Class A without duplicating their code in Class B. Keep in mind that Class B still doesn't contain the methods of Class A; it simply uses an instance of Class A to access and execute those methods.
This is where inheritance comes into play. By using inheritance and having Class B extend Class A, Class B can inherit and execute all the methods of Class A without code duplication and without the need to create a separate object for Class A's methods. Let's see the code
class A{
void m1(){
System.out.println("Hello World");
}
}
class B extends A{
void m2(){
System.out.println("Hello Java");
}
}
public class Main
{
public static void main(String[] args) {
B b = new B();
b.m1();
b.m2();
}
}
Output:
Hello World
Hello Java
Here the class extending another class is referred to as the child class, while the class being extended is known as the parent class. In the above code, Class B is the child class, and Class A is the parent class.
Benefits of Using Inheritance:
Code Reusability: Inheritance allows you to reuse the code from parent class to child class.
Extensibility: Child classes can extend the functionality of parent classes by adding new attributes and methods or by modifying existing ones.
Types of Inheritance in Java :
In Java, there are a total of 5 inheritance types.
Single Inheritance: A sub-class will be derived from one parent class it is also called Simple Inheritance.
Let's see an example code for better understanding...
class A{ void m1(){ System.out.println("Hello World"); } } class B extends A{ void m2(){ System.out.println("Hello Java"); } public static void main(String[] args){ B b = new B(); b.m1(); b.m2(); } }
Output: Hello World Hello Java
Multilevel Inheritance: A sub-class is derived from a class that is itself a sub-class of another class.
Let's see the example code
class A{ void m1(){ System.out.println("Hello World"); } } class B extends A{ void m2(){ System.out.println("Hello Java"); } } class C extends B{ void m3(){ System.out.println("Java World"); } public static void main(String[] args){ C c = new C(); c.m1(); c.m2(); c.m3(); } }
Output: Hello World Hello Java Java World
Hierarchical Inheritance: Multiple sub-classes are derived from one parent class(Base class).
class A{ void m1(){ System.out.println("Hello World"); } } class B extends A{ void m2(){ System.out.println("Hello Java"); } } class C extends A{ void m3(){ System.out.println("Java World"); } public static void main(String[] args){ C c = new C(); c.m1(); c.m2(); c.m3(); } }
error: cannot find symbol b.m2()
The reason for the error is that both Class B and Class C are subclasses of Class A. As a result, they inherit only the behavior of Class A and not that of other classes.
Multiple Inheritance (through Interfaces): Java does not support multiple inheritance directly via classes, but it can be achieved through interfaces.
- Hybrid (or Mixin) Inheritance: Hybrid inheritance typically involves combining different forms of inheritance, such as single inheritance, multilevel inheritance, and hierarchical inheritance, within the same class hierarchy. It's a way to create a more flexible and complex relationship structure by using various inheritance mechanisms to meet specific design and functionality requirements.
Based on the above picture, it can be concluded that hybrid inheritance can be achieved by combining single inheritance with multilevel inheritance through classes. Let's see an example code
class A{
void m1(){
System.out.println("Hello World");
}
}
class B extends A{
void m2(){
System.out.println("Hello Java");
}
}
class D extends B{
void m4(){
System.out.println("Welcome to Java World");
}
}
class C extends B{
void m3(){
System.out.println("Java World");
}
public static void main(String[] args){
C c = new C();
c.m1();
c.m2();
c.m3();
System.out.println();
D d = new D();
d.m1();
d.m2();
d.m4();
}
}
Output:
Hello World
Hello Java
Java World
Hello World
Hello Java
Welcome to Java World
We can also achieve hybrid inheritance using interfaces. In Java, interfaces provide a way to inherit multiple sets of behaviors into a class, allowing you to combine different types of inheritance relationships. By implementing multiple interfaces in a class, you can inherit behaviors and methods from each interface, effectively achieving hybrid inheritance.
In summary, hybrid (or mixin) inheritance in Java is flexible. It lets you mix different types of inheritance within a class structure. You can do this by combining classes with single and multilevel inheritance. Alternatively, you can use interfaces to achieve the same effect. This flexibility empowers developers to design their code to fit their program's needs and complexity.
Polymorphism in Java:
What is Polymorphism and why do we need it?
Polymorphism in Java means performing the same task in different forms. The term 'polymorphism' combines 'poly,' meaning 'many,' and 'morphism,' meaning 'forms.' It enables you to write code that can operate on object methods or behaviors based on the context or input passed to it.
In simpler words, polymorphism is a mechanism where the method to be called is decided based on the input passed to it. Let's understand this with a simple example. Imagine you're doing a calculation. Sometimes you use only whole numbers (int values), and you expect the result to be a whole number. Other times, you might use decimal values, or a mix of both whole and decimal values, and each time you expect a different result. That's where polymorphism comes into play. Depending on the input values, it will call the appropriate methods and give you an accurate result.
Types of Polymorphism in Java:
Inheritance is a key factor in achieving polymorphism and there are two types of polymorphism in Java.
Runtime Polymorphism: This is achieved through
method overriding
, where the method to be executed is decided at runtime based on the actual object. To achieve this, the subclass must override a method that is defined in its superclass, creating an IS-A relationship between the two classes. If you are thinking about what is method overriding and why do we need to override the existing method? Then let's understand this with a real-time example Imagine that you are building an application feature for a smartphone. In the previous version, the phone lock feature code was written password-based now you are thinking of changing the approach by finger-print and you want all the other features of the previous version, the best approach for such a situation would be creating a class from the existing class (nothing but Inheritance approach) as it will be used for code reusability so that we can have all previous code and as child class acquires all the behavior of parent class now make the changes only in the needed part in child class by annotating the method with@Overrding
annotation.class SmartPhone_V1{ void wifiFeature() { System.out.println("Some logic related to wifi"); } void bluetooth() { System.out.println("Some logic related to Bluetooth"); } void processor() { System.out.println("Some related to Processor"); } void camera() { System.out.println("Camera "); } void phoneLockFeature() { System.out.println("Phone lock authentication using Password"); } } public class SmartPhone_V2 extends SmartPhone_V1{ @Override void phoneLockFeature() { System.out.println("Phone lock authentication using FingerPrint"); } public static void main(String[] args) { SmartPhone_V2 v2 = new SmartPhone_V2(); v2.phoneLockFeature(); v2.bluetooth(); } }
Output: Phone lock authentication using FingerPrint Some logic related to Bluetooth
Compile-time Polymorphism: This is achieved through
method overloading
, where multiple methods with the same name but different parameters can coexist in a class. The appropriate method to be executed is determined at compile-time based on the method signature and arguments provided. This type of polymorphism does not require an IS-A relationship between classes but relies on method signature differences. If you are thinking about what is method overloading and why do we need to use method overloading? Then let's understand this with a real-time example. Imagine you are building a book management system where users might search for a book using the book title, others might search using the author's first and last name, and some might search using the year of publication. The best approach for situations where one task can be performed in different ways based on different parameters is to create different methods or behaviors in the class so that all situations are covered, and users get the desired output.public class BooksManagementSystem { //Method to search for a books by title void search(String title) { System.out.println("Searching for books with title: " + title); } // Method to search for books by author void search(String authorFirstName, String authorLastName) { System.out.println("Searching for books by author: " + authorFirstName + " " + authorLastName); } // Method to search for books by publication year void search(int publicationYear) { System.out.println("Searching for books published in year: "+publicationYear); } public static void main(String[] args) { BooksManagementSystem managementSystem = new BooksManagementSystem(); managementSystem.search("Core Java"); managementSystem.search(1925); managementSystem.search("R. Nageswara", "Rao"); } }
Output: Searching for books with title: Core Java Searching for books published in year: 1925 Searching for books by author: R. Nageswara Rao
Some Common mistakes while working with Inheritance.
Creating an object of the parent class but trying to evoke/call the method of the child class. Let's see an example code
class Parent{ void m1() { System.out.println("Parent Class Method"); } } class Child extends Parent{ void m2() { System.out.println("Child Class Method"); } } public class Main { public static void main(String[] args) { Parent p = new Parent(); p.m1(); p.m2(); //This will result in a compilation error } }
The reason why it will not work is because you are creating an object of the
Parent
class and then trying to call them2()
method, which is defined in theChild
class. Since theParent
class does not have am2()
method, this will result in a compilation error.Creating an object of the child class assigning to the parent type variable and then trying to call/evoke the child method. Let's see the example code
class Parent{ void m1() { System.out.println("Parent Class Method"); } } class Child extends Parent{ void m2() { System.out.println("Child Class Method"); } } public class Main { public static void main(String[] args) { Parent p=new Child(); p.m1(); p.m2();//This will result in a compilation error } }
Creating an object of the Parent class assigning to the Child type variable. Let's see the example code
class Parent{
void m1() {
System.out.println("Parent Class Method");
}
}
class Child extends Parent{
void m2() {
System.out.println("Child Class Method");
}
}
public class Main {
public static void main(String[] args) {
Child p=new Parent(); //This line causes a compilation error
p.m1();
p.m2();
}
}
Solutions for the mistake
Those are some common mistakes we make while working with inheritance. So if you are thinking what is the right way of using inheritance then it's quite simple if you create an object of the Parent class and then assign it to the parent type variable so that it will only evoke all the Parent class Methods and cannot evoke the methods of Child Class because those methods are define in Child Class.
Parent p = new Parent(); p.m1();
If you are creating an object of Child Class and assigning it to the Parent class type variable then you can evoke only Parent class methods why? because the variable can only hold the Parent methods even though you are assigning the child object.
Parent p = new Child(); p.m1();
If you want to invoke all the methods of both the Parent and Child classes, create an object of the Child class and assign it to a variable of the Child class type; it will invoke all the methods.
Child p = new Child(); p.m1(); p2.m2();
Summary:
In this blog, we explored the foundations of Inheritance and Polymorphism in Java. We began by understanding the 'what' and 'why' of Inheritance, followed by practical examples and the benefits it brings to software development. We then ventured into the various Types of Inheritance and transitioned to Polymorphism, learning its significance and types.
We didn't stop there; we also discussed common mistakes made while working with Inheritance and provided effective Solutions to tackle them. Armed with this knowledge, you're now equipped to build more elegant and efficient Java code while embracing the power of Inheritance and Polymorphism in your projects.
If you found this blog informative, please consider giving it a like. Your support is greatly appreciated and serves as motivation for me to write more blogs like this. You can also subscribe for regular updates.
Subscribe to my newsletter
Read articles from Dhanjeet Kumar Thakur directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Dhanjeet Kumar Thakur
Dhanjeet Kumar Thakur
I'm Dhanjeet Kumar Thakur, a dedicated backend developer skilled in Core Java, Spring frameworks, Hibernate, and web tech like HTML, CSS, and JavaScript. With a sharp eye for detail, I create strong backend solutions for seamless user experiences. Git and GitHub are my allies for effective collaboration and code integrity. Always learning, I'm on a mission to elevate my skills and make an impact in Java backend development.