Understanding Inheritance in Java
Inheritance is one of the fundamental concepts of Object-Oriented Programming (OOP). It allows one class to inherit properties and behaviors from another class.
According to Oracle's Java documentation, inheritance is a mechanism by which one object acquires the properties and behaviors of a parent object.
In simpler terms, inheritance allows code reuse.
Instead of writing the same methods again and again in multiple classes, we can place common functionality inside a parent class and allow child classes to inherit those features.
Think about mobile phones.
Every smartphone has some common features:
- Calling
- Internet
- Contacts
- Messaging
If Samsung, OnePlus, Vivo, and Pixel all need these features, it would be inefficient to rewrite them in every class.
Instead, we create a common parent class called Mobile.
Then individual brands can extend that class and inherit all common functionality automatically.
This reduces code duplication, improves maintainability, and makes applications easier to scale as new subclasses are added.
Visually:
Mobile
↑
Samsung
This relationship means:
Samsung IS-A Mobile.
The IS-A relationship is one of the easiest ways to identify inheritance in Java.
Since a Samsung phone is a type of Mobile, it can inherit everything available inside the Mobile class.
What Happens When We Create a Samsung Object?
Consider this statement:
Samsung samsung = new Samsung("Galaxy");
Many beginners assume Java immediately starts executing the Samsung constructor because we are creating a Samsung object.
However, that is not what actually happens internally.
Object creation in Java follows a specific sequence.
Java cannot directly initialize Samsung-specific data because Samsung depends on Mobile.
Since Samsung inherits from Mobile, Java must first ensure that the Mobile portion of the object is properly initialized.
Think about constructing a building.
You cannot start with the roof.
You need:
Foundation
↓
Walls
↓
Roof
Similarly, Java follows:
Parent Class
↓
Child Class
The parent acts as the foundation for the child.
Without initializing the parent, the child object would be incomplete.
This is why Java always starts with the parent constructor before executing the child constructor.
Why Does the Parent Constructor Execute First?
This is one of the most common interview questions.
When a Samsung object is created, the object actually contains two logical parts:
Samsung Object
├── Mobile Part
└── Samsung Part
The Mobile part comes from inheritance.
Because that parent portion exists inside the object, Java must initialize it first.
Imagine buying a Samsung phone.
Before Samsung-specific features can work, the phone must first have the basic mobile functionality available.
The same principle applies during object creation.
Java first prepares everything inherited from the parent class and then initializes child-specific members.
This process is called Constructor Chaining.
Constructor chaining refers to the sequence in which constructors are called during object creation.
In an inheritance hierarchy, constructor execution always proceeds from parent to child.
This ensures that objects are created in a valid and predictable state.
Understanding the super Keyword
The super keyword is one of the most important concepts in inheritance.
Many beginners memorize that:
super(name);
calls the parent constructor.
While that statement is correct, it is important to understand what is actually happening behind the scenes.
The super keyword refers to the immediate parent class.
When Java encounters:
super(name);
it temporarily transfers control to the parent constructor.
In our example:
public Samsung(String name){
super(name);
System.out.println("samsung-constructor");
}
Java performs the following steps:
Step 1:
Enter Samsung constructor
Step 2:
Encounter super(name)
Step 3:
Move to Mobile constructor
Step 4:
Execute Mobile constructor
Step 5:
Return to Samsung constructor
Step 6:
Execute remaining statements
Because of this sequence, the output becomes:
mobile-constructor Galaxy
samsung-constructor
instead of:
samsung-constructor
mobile-constructor
The parent constructor always finishes first.
What Happens If We Don't Write super()?
Many developers are surprised to learn that Java automatically inserts super() when it is missing.
For example:
public Samsung(String name){
System.out.println("samsung-constructor");
}
Java internally converts it to:
public Samsung(String name){
super();
System.out.println("samsung-constructor");
}
This is called an implicit constructor invocation.
The compiler automatically inserts a call to the parent constructor because Java wants to ensure the parent portion of the object is initialized.
This behavior happens even if you don't explicitly write super() yourself.
Many beginners assume the parent constructor runs magically.
The truth is that Java inserts the constructor call on your behalf.
Why Must super() Be the First Statement?
Java enforces a strict rule:
super(...)
must always be the first statement inside a constructor.
Valid:
public Samsung(String name){
super(name);
System.out.println("hello");
}
Invalid:
public Samsung(String name){
System.out.println("hello");
super(name);
}
Compilation error.
But why is Java so strict?
The reason is object consistency.
Before the child constructor executes any logic, Java must ensure that the parent class has completed initialization.
If Java allowed statements before super(), the child constructor might access data that depends on the parent being initialized.
This could lead to partially constructed objects and unpredictable behavior.
To avoid such issues, Java enforces a rule that parent constructor invocation must happen first.
This guarantees that the inheritance hierarchy is initialized from top to bottom.
What Can We Pass Inside super()?
The values passed to super() must match a constructor available in the parent class.
Example:
Parent:
public Mobile(String name){
}
Child:
super(name);
Java searches for:
Mobile(String name)
and invokes it.
If the parent constructor expects multiple parameters:
public Mobile(String name,int price){
}
then:
super(name,price);
must be used.
A common beginner mistake is passing arguments that do not match any parent constructor.
When this happens, Java throws a compilation error because it cannot determine which constructor should be called.
Always think of super() as:
"Call a constructor from my parent class."
Method Overriding
Now let's examine these methods:
Inside Mobile:
public void internet(){
System.out.println("internet mobile class");
}
Inside Samsung:
public void internet(){
System.out.println("internet samsung class");
}
Notice that both methods have:
- Same name
- Same parameter list
- Compatible return type
This is called Method Overriding.
Method overriding allows a subclass to provide its own implementation of a method already defined in its superclass.
The parent method still exists.
The child simply replaces the inherited behavior with a more specific version.
Think about internet connectivity.
Every mobile phone supports internet access.
However, Samsung may implement that functionality differently from another manufacturer.
The capability remains the same, but the implementation changes.
That is exactly what method overriding achieves.
Why Does Samsung's Method Execute?
Consider:
Samsung samsung = new Samsung("Galaxy");
samsung.internet();
Java checks the object type.
The object is:
Samsung
Therefore Java executes:
Samsung.internet()
Output:
internet samsung class
Even though the method originally existed inside Mobile, Samsung has provided a newer implementation.
Java always prefers the most specific implementation available.
Runtime Polymorphism Explained
One of the most powerful features of Java is runtime polymorphism.
The word comes from Greek:
Poly = Many
Morph = Forms
Meaning:
One Interface
Many Behaviors
Consider:
Mobile phone = new Samsung("Galaxy");
phone.internet();
Many beginners think:
Reference Type = Mobile
Therefore:
Mobile.internet()
should run.
But that is incorrect.
Java checks:
Actual Object Type
which is:
Samsung
Because the actual object is Samsung, Java executes:
Samsung.internet()
This decision happens while the program is running.
That is why it is called Runtime Polymorphism.
Dynamic Method Dispatch
The technical name used in Java documentation is:
Dynamic Method Dispatch
This is the mechanism through which Java determines which overridden method should execute.
At compile time Java only verifies that:
phone.internet();
is a valid method call.
The actual implementation is selected later during runtime.
This dynamic selection enables flexible and extensible application design.
Frameworks such as Spring, Hibernate, and Android rely heavily on runtime polymorphism.
What Actually Happens in Memory?
Let's analyze:
Mobile phone = new Samsung("Galaxy");
Behind the scenes:
Step 1
Memory is allocated on the heap for a Samsung object.
Step 2
Space is reserved for:
- Mobile fields
- Samsung fields
- Internal object metadata
Step 3
Java begins constructor execution.
Step 4
Parent constructor executes.
Step 5
Child constructor executes.
Step 6
Reference variable receives the object reference.
Visually:
phone
│
▼
Samsung Object
├── Mobile Data
└── Samsung Data
When:
phone.internet();
is executed, Java inspects the actual object in memory.
Since the object is Samsung, Samsung's overridden method is selected.
This entire decision happens dynamically during runtime.
Can We Change the Return Type While Overriding?
Generally, no.
Example:
Parent:
public void internet(){
}
Child:
public int internet(){
}
Compilation error.
The method signature becomes incompatible.
However, Java supports something called a Covariant Return Type.
Example:
public Mobile getPhone(){
}
Child:
public Samsung getPhone(){
}
This is allowed because Samsung is a subtype of Mobile.
Covariant return types provide flexibility while still preserving type safety.
Final Takeaway
Whenever you create:
Samsung samsung = new Samsung("Galaxy");
Java follows this sequence:
1. Allocate memory
2. Initialize parent class
3. Execute parent constructor
4. Execute child constructor
5. Finish object creation
6. Execute methods
7. Use runtime polymorphism for overridden methods
The easiest way to remember everything is:
Constructors move from Parent → Child, while overridden methods are chosen based on the Actual Object at Runtime.
Once this concept clicks, inheritance, constructor chaining, super(), method overriding, dynamic method dispatch, and runtime polymorphism become much easier to understand and debug in real-world Java applications.
Top comments (0)