DEV Community

Cover image for I Didn’t Understand Polymorphism Until This One Line of Code
Haruka
Haruka

Posted on

I Didn’t Understand Polymorphism Until This One Line of Code

When I saw this line in my paper-based quiz, I had no idea what it would do:

Ramen r = new TonkotsuRamen();
System.out.println(r);
Enter fullscreen mode Exit fullscreen mode

I thought I understood polymorphism, but this simple line proved me wrong.

The Example I Was Working With

To better understand the concept, I recreated a simplified version of the problem:

abstract class Ramen {
    public abstract String getName();
 
    public String serve() {
        return "Serving " + getName();
    }
}
 
class TonkotsuRamen extends Ramen {
    @Override
    public String getName() {
        return "Tonkotsu Ramen";
    }
 
    @Override
    public String serve() {
        return super.serve() + " 🍜 Rich and creamy!";
    }
}
 
class ShoyuRamen extends Ramen {
    @Override
    public String getName() {
        return "Shoyu Ramen";
    }
}
Enter fullscreen mode Exit fullscreen mode

What I Thought Would Happen

At first, I had no idea what would happen, so I guessed it would call the serve() method and print:

Serving Tonkotsu Ramen 🍜 Rich and creamy! 
Enter fullscreen mode Exit fullscreen mode

What Actually Happened

Instead, Java printed something like:

TonkotsuRamen@7344699f
Enter fullscreen mode Exit fullscreen mode

That’s when I realized I didn’t understand what was going on.
This output comes from the default toString() implementation in the Object class.

At first, I thought this was just about toString().
But later, I realized this confusion actually came from a deeper misunderstanding of polymorphism.

Why I Was Confused

There were a few things I didn’t fully understand:

  • I didn’t know what System.out.println(object) actually does
  • I didn’t clearly understand the difference between the reference type and the actual object
  • I wasn’t sure which method was being called

Coming from a JavaScript background made this even more confusing, because console.log() behaves differently and doesn’t rely on toString() in the same way.

The Key Realization

The turning point for me was understanding this:

The reference type determines what you can call, and the actual object determines what runs.

Also, in Java:
System.out.println(object) actually calls object.toString() behind the scenes.
Since it didn’t override toString(), Java used the default implementation from the Object class.

What I Was Missing About Polymorphism

At this point, I also realized something deeper:

Polymorphism is not just about inheritance — it's about behavior at runtime.

Even though the variable is declared as:

Ramen r
Enter fullscreen mode Exit fullscreen mode

The actual object is:

new TonkotsuRamen()
Enter fullscreen mode Exit fullscreen mode

So when you call:

r.serve();
Enter fullscreen mode Exit fullscreen mode

Java doesn’t run the method based on the reference type (Ramen), it runs it based on the actual object (TonkotsuRamen) at runtime.

In other words:

Polymorphism means the same method call can behave differently depending on the actual object.

Compile-Time vs Runtime (This Was the Missing Piece)

Ramen r = new TonkotsuRamen();
r.serve();
Enter fullscreen mode Exit fullscreen mode
  • At compile time, Java checks if serve() exists in Ramen

  • At runtime, Java decides which implementation to run → TonkotsuRamen

This is why:

r.serve();
Enter fullscreen mode Exit fullscreen mode

actually runs:

TonkotsuRamen.serve()
Enter fullscreen mode Exit fullscreen mode

The important takeaway:

Polymorphism is not about the variable type — it's about which implementation runs at runtime.

Making It Work the Way I Expected

@Override
public String toString() {
    return serve();
}
Enter fullscreen mode Exit fullscreen mode

Now the output displays:

Serving Tonkotsu Ramen 🍜 Rich and creamy! 
Enter fullscreen mode Exit fullscreen mode

Why This Matters in Real Development

At first, I didn’t see how this concept would be useful in real development.

However, I later realized that polymorphism helps eliminate large conditional statements.

For example, instead of writing code like this:

if (type.equals("tonkotsu")) {
// ...

} else if (type.equals("shoyu")) {
// ...

}
Enter fullscreen mode Exit fullscreen mode

We can rely on polymorphism:

Ramen r = new TonkotsuRamen();
r.serve();
Enter fullscreen mode Exit fullscreen mode

Each class handles its own behavior, making the code cleaner and easier to extend.

This Also Applies to Frontend Development

This concept also applies to frontend development.
In React, instead of writing complex conditional logic, we often separate behavior into reusable components.

For example, instead of:

if (type === "tonkotsu") {
  return <Tonkotsu />;
} else if (type === "shoyu") {
  return <Shoyu />;
}
Enter fullscreen mode Exit fullscreen mode

We design components so that each one handles its own behavior.

Same idea as polymorphism:

  • Avoid large conditionals
  • Let each unit handle its own logic

This makes the code more maintainable and scalable.

Final Thoughts

What once felt like a confusing quiz question turned out to be a practical concept I can use to write cleaner code. Instead of relying on conditional logic, I can use polymorphism to make my code easier to extend and maintain. Sometimes, one small line of code reveals a big gap in understanding, and fixing that gap can completely change how you think about software.

Top comments (0)