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);
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";
}
}
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!
What Actually Happened
Instead, Java printed something like:
TonkotsuRamen@7344699f
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
The actual object is:
new TonkotsuRamen()
So when you call:
r.serve();
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();
At compile time, Java checks if
serve()exists in RamenAt runtime, Java decides which implementation to run →
TonkotsuRamen
This is why:
r.serve();
actually runs:
TonkotsuRamen.serve()
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();
}
Now the output displays:
Serving Tonkotsu Ramen 🍜 Rich and creamy!
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")) {
// ...
}
We can rely on polymorphism:
Ramen r = new TonkotsuRamen();
r.serve();
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 />;
}
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)