DEV Community

Cover image for From super() to Runtime Polymorphism: A Beginner's Guide to Java Inheritance
Kathirvel S
Kathirvel S

Posted on

From super() to Runtime Polymorphism: A Beginner's Guide to Java Inheritance

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
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Similarly, Java follows:

Parent Class
   ↓
Child Class
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

it temporarily transfers control to the parent constructor.

In our example:

public Samsung(String name){
    super(name);
    System.out.println("samsung-constructor");
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Because of this sequence, the output becomes:

mobile-constructor Galaxy
samsung-constructor
Enter fullscreen mode Exit fullscreen mode

instead of:

samsung-constructor
mobile-constructor
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

Java internally converts it to:

public Samsung(String name){
    super();
    System.out.println("samsung-constructor");
}
Enter fullscreen mode Exit fullscreen mode

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(...)
Enter fullscreen mode Exit fullscreen mode

must always be the first statement inside a constructor.

Valid:

public Samsung(String name){
    super(name);
    System.out.println("hello");
}
Enter fullscreen mode Exit fullscreen mode

Invalid:

public Samsung(String name){

    System.out.println("hello");

    super(name);
}
Enter fullscreen mode Exit fullscreen mode

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){
}
Enter fullscreen mode Exit fullscreen mode

Child:

super(name);
Enter fullscreen mode Exit fullscreen mode

Java searches for:

Mobile(String name)
Enter fullscreen mode Exit fullscreen mode

and invokes it.

If the parent constructor expects multiple parameters:

public Mobile(String name,int price){
}
Enter fullscreen mode Exit fullscreen mode

then:

super(name,price);
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

Inside Samsung:

public void internet(){
    System.out.println("internet samsung class");
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

Java checks the object type.

The object is:

Samsung
Enter fullscreen mode Exit fullscreen mode

Therefore Java executes:

Samsung.internet()
Enter fullscreen mode Exit fullscreen mode

Output:

internet samsung class
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Meaning:

One Interface
Many Behaviors
Enter fullscreen mode Exit fullscreen mode

Consider:

Mobile phone = new Samsung("Galaxy");

phone.internet();
Enter fullscreen mode Exit fullscreen mode

Many beginners think:

Reference Type = Mobile
Enter fullscreen mode Exit fullscreen mode

Therefore:

Mobile.internet()
Enter fullscreen mode Exit fullscreen mode

should run.

But that is incorrect.

Java checks:

Actual Object Type
Enter fullscreen mode Exit fullscreen mode

which is:

Samsung
Enter fullscreen mode Exit fullscreen mode

Because the actual object is Samsung, Java executes:

Samsung.internet()
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

When:

phone.internet();
Enter fullscreen mode Exit fullscreen mode

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(){
}
Enter fullscreen mode Exit fullscreen mode

Child:

public int internet(){
}
Enter fullscreen mode Exit fullscreen mode

Compilation error.

The method signature becomes incompatible.

However, Java supports something called a Covariant Return Type.

Example:

public Mobile getPhone(){
}
Enter fullscreen mode Exit fullscreen mode

Child:

public Samsung getPhone(){
}
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)