Method Overriding and Runtime Polymorphism
Override parent methods with `@Override`, see how runtime dispatch picks the child implementation, and keep the parent contract intact.
আপনি একটি free preview lesson দেখছেন।
Lesson Overview
So far a child class has inherited its parent's behavior unchanged. Often, though, each child needs to do the same job its own way. That is method overriding, one of the most important behaviors of inheritance. Suppose we need to know the estimated learning time for every type of lesson content:
VideoLesson
→ video duration
ArticleLesson
→ reading time
QuizLesson
→ question count
The operation, calculateEstimatedMinutes(), is the same for every type of content, but the implementation is different:
ContentItem content = new VideoLesson(1L, "Method Overriding",
"https://cdn.tarkev.com/videos/overriding", 18);
System.out.println(content.calculateEstimatedMinutes());
Even though the reference type is ContentItem, the actual VideoLesson implementation executes at runtime. This is runtime polymorphism: callers use one common operation, and each object supplies its own behavior.
Learning Objectives
After completing this lesson, you will be able to:
- Override a parent method in a child class
- Use
@Overridecorrectly - Explain runtime method dispatch
- Execute child implementations through a parent reference
- Use
super.method() - Distinguish overriding from overloading
- Identify a valid override signature
- Design child behavior while preserving the parent contract
What Is Method Overriding?
When a child class provides a new implementation of an inherited instance method from the parent class, it is called method overriding. Parent:
public class ContentItem {
public int calculateEstimatedMinutes() {
return 0;
}
}
Child:
public class VideoLesson extends ContentItem {
private final int durationInMinutes;
public VideoLesson(long id, String title, int durationInMinutes) {
super(id, title);
this.durationInMinutes = durationInMinutes;
}
@Override
public int calculateEstimatedMinutes() {
return durationInMinutes;
}
}
VideoLesson provides its own implementation instead of using the parent implementation. To override a method, generally:
- Method name must be the same
- Parameter list must be the same
- Return type must be compatible
- Access level must be compatible
- Parent method must be overridable
The VideoLesson method above meets all of these, so it is a valid override.
The @Override Annotation
Put @Override before an overriding method, as VideoLesson does above. This tells the compiler:
This method is intended to override an inherited method.
Why does that matter? Suppose the parent method is:
public int calculateEstimatedMinutes() {
return 0;
}
The child accidentally writes:
public int calculateEstimateMinutes() {
return durationInMinutes;
}
The names are different: calculateEstimatedMinutes and calculateEstimateMinutes. Without @Override, this compiles as a separate new method, and VideoLesson silently keeps the parent's return 0. With:
@Override
public int calculateEstimateMinutes() {
return durationInMinutes;
}
the compiler reports an error:
error: method does not override or implement a method from a supertype
Useful rule:
Use
@Overridewith every method that is intended to override another method.
One Parent, Three Overrides
A Common Parent Type
We add one method to the ContentItem from the previous lessons (id, title, publish() and the getters stay as they were):
public int calculateEstimatedMinutes() {
return 0;
}
For now, calculateEstimatedMinutes() returns 0 by default. Later, after learning abstract methods, this design can be improved further.
Overriding in Each Child
For a VideoLesson, the estimate is simply its duration, as in the override above. An ArticleLesson with a wordCount field estimates from reading speed:
private static final int WORDS_PER_MINUTE = 200;
@Override
public int calculateEstimatedMinutes() {
int estimatedMinutes = wordCount / WORDS_PER_MINUTE;
return Math.max(1, estimatedMinutes);
}
A QuizLesson with a questionCount field allows two minutes per question:
private static final int MINUTES_PER_QUESTION = 2;
@Override
public int calculateEstimatedMinutes() {
return questionCount * MINUTES_PER_QUESTION;
}
The constructors validate these fields as usual; the full classes are in the Complete Example below. The same operation, calculateEstimatedMinutes(), provides different behavior for different types.
Parent Reference, Child Object
With ContentItem content = new VideoLesson(1L, "Runtime Polymorphism", "https://cdn.tarkev.com/videos/runtime", 18); the compile-time type is ContentItem and the runtime type is VideoLesson. Now int minutes = content.calculateEstimatedMinutes(); gives 18: the VideoLesson implementation executed.
Runtime Method Dispatch
The compiler first checks: does ContentItem have calculateEstimatedMinutes()? Yes, so the call is allowed. At runtime, Java then looks at the actual object, a VideoLesson, and executes the most specific overridden implementation. Conceptually:
Diagram আঁকা হচ্ছে…
Runtime Polymorphism
Multiple child objects can be used through parent-type references:
ContentItem video = new VideoLesson(1L, "Video",
"https://example.com/video", 20);
ContentItem article = new ArticleLesson(2L, "Article", 1_000);
ContentItem quiz = new QuizLesson(3L, "Quiz", 10);
System.out.println(video.calculateEstimatedMinutes());
System.out.println(article.calculateEstimatedMinutes());
System.out.println(quiz.calculateEstimatedMinutes());
Output:
20
5
20
Same method call. Different runtime behavior.
Why Is Polymorphism Useful?
Without polymorphism, the caller might write conditions based on type:
if (type.equals("VIDEO")) {
// video calculation
} else if (type.equals("ARTICLE")) {
// article calculation
} else if (type.equals("QUIZ")) {
// quiz calculation
}
The caller must know the details of every content type. With polymorphism, the caller writes int minutes = content.calculateEstimatedMinutes(); and each object knows how to perform its own calculation. The caller only needs to know the common contract.
One Method, Multiple Implementations
private static void printEstimate(ContentItem content) {
System.out.println(content.calculateEstimatedMinutes());
}
Call:
printEstimate(video);
printEstimate(article);
printEstimate(quiz);
printEstimate() does not check the child type. Runtime dispatch selects the appropriate implementation.
Calling Parent Behavior with super.method()
A child override can reuse the parent implementation. Parent:
public String createSummary() {
return "Title: " + getTitle();
}
Child:
@Override
public String createSummary() {
return super.createSummary() + ", Type: Video";
}
Here, super.createSummary() explicitly calls the parent implementation. A child can either replace or extend parent behavior. This override completely replaces the parent implementation:
@Override
public int calculateEstimatedMinutes() {
return durationInMinutes;
}
This one reuses the parent result and adds more information:
@Override
public String createSummary() {
return super.createSummary()
+ ", Duration: " + durationInMinutes + " minutes";
}
When should you use super.method()? Use it when the child genuinely wants to preserve or extend the parent behavior. Do not call super.someMethod() mechanically. Ask:
Is the parent implementation a meaningful part of the child behavior?
Overriding vs. Overloading
These are different concepts. Overriding means a parent-child relationship, the same method signature, and runtime-selected behavior; calculateEstimatedMinutes() in VideoLesson is an example. Overloading, which we learned earlier, means the same method name with a different parameter list:
public void publish() {
}
public void publish(boolean notifyLearners) {
}
This is not runtime polymorphism.
| Overriding | Overloading |
|---|---|
| Parent-child relationship | Parent-child relationship not required |
| Same parameter list | Different parameter list |
| Runtime behavior selection | Compile-time method selection |
| Specializes inherited behavior | Provides multiple calling forms |
Uses @Override | Does not use @Override |
Same Name Does Not Always Mean Override
Parent:
public void publish() {
}
Child:
public void publish(boolean notify) {
}
This is not overriding. It is another overload, and the inherited publish() still exists.
Return Type and Parameter Rules
Return Type Rules
The same return type is always valid. For object return types, a child can return a more specific subtype. This is called a covariant return type. Parent:
public ContentItem copy() {
return this;
}
Child:
@Override
public VideoLesson copy() {
return this;
}
For now, remember:
An override return type must be compatible with the parent method.
A primitive return type must stay exactly the same. Parent:
public int calculateEstimatedMinutes() {
return 10;
}
Child (does not compile):
@Override
public long calculateEstimatedMinutes() {
return 10L;
}
error: calculateEstimatedMinutes() in VideoLesson cannot override calculateEstimatedMinutes() in ContentItem
return type long is not compatible with int
Parameters Must Match
Parent:
public void updateTitle(String title) {
}
Child:
public void updateTitle(Object title) {
}
This is not an override, because the parameter type changed. Correct:
@Override
public void updateTitle(String title) {
}
Visibility Cannot Be Reduced
Parent:
public void publish() {
}
The child cannot write:
@Override
protected void publish() {
}
because the parent promised a public operation:
error: publish() in VideoLesson cannot override publish() in ContentItem
attempting to assign weaker access privileges; was public
An override can keep or widen the parent's access level (for example, protected to public), but never narrow it. Why? The caller may have:
ContentItem content = new VideoLesson(...);
content.publish();
The parent contract says this operation is public. The child cannot make that contract less accessible.
What Is Not Overridden
Private Methods Are Not Overridden
Parent:
private void validate() {
}
Child:
private void validate() {
}
These are separate methods. The parent private method is not visible to the child, so no overriding occurs. Adding @Override to the child method would be a compile error.
final Methods Cannot Be Overridden
Parent:
public final long getId() {
return id;
}
The child cannot redefine it:
@Override
public long getId() {
return 100L;
}
Compile error:
error: getId() in VideoLesson cannot override getId() in ContentItem
overridden method is final
Constructors Are Not Overridden
Even though the parameter lists of the parent constructor ContentItem(long id) and the child constructor VideoLesson(long id) look the same, constructors do not override one another. Constructors initialize their own classes.
Static Methods Are Not Runtime Polymorphic
Suppose the parent has:
public static String getCategory() {
return "CONTENT";
}
Child:
public static String getCategory() {
return "VIDEO";
}
This is not overriding. The child's static method hides the parent's static method.
A static method is class-level behavior. Java chooses it at compile time from the declared type, not from the runtime object. With ContentItem content = new VideoLesson(...), the call content.getCategory() runs the ContentItem version and returns "CONTENT". Putting @Override on a static method is a compile error (static methods cannot be annotated with @Override). Preferred usage:
ContentItem.getCategory();
VideoLesson.getCategory();
Do not depend on static methods for runtime polymorphic behavior.
Fields Are Not Runtime Polymorphic
Avoid designing polymorphic behavior with same-name fields. Parent:
public String type = "CONTENT";
Child:
public String type = "VIDEO";
With ContentItem content = new VideoLesson(...);, field access is based on the declared reference type, not runtime method dispatch: content.type reads "CONTENT", even though the object is a VideoLesson. For polymorphic behavior, use a method instead:
public String getContentType() {
return "CONTENT";
}
Child:
@Override
public String getContentType() {
return "VIDEO";
}
Parent Contract Must Still Hold
A method can be syntactically valid and still be a bad override. Suppose the parent contract is:
calculateEstimatedMinutes()
→ returns zero or a positive estimate
Bad child:
@Override
public int calculateEstimatedMinutes() {
return -100;
}
The compiler accepts the signature, but the behavior breaks the parent contract.
The Compiler Checks Syntax, Not Domain Meaning
The compiler can check:
- Method name
- Parameters
- Return type
- Visibility
@Override
The compiler cannot fully decide:
- Whether the result is meaningful
- Whether domain invariants are preserved
- Whether the method unexpectedly changes unrelated state
That is a design responsibility.
Avoid Surprising Side Effects
calculateEstimatedMinutes() sounds like a query. Bad override:
@Override
public int calculateEstimatedMinutes() {
publish();
return durationInMinutes;
}
The caller asked for an estimate. It did not ask to publish the content. An override should preserve the expected meaning of the parent operation.
If a Child Cannot Support Parent Behavior
Suppose the parent guarantees public boolean publish() for all content. A child that must do:
@Override
public boolean publish() {
throw new UnsupportedOperationException();
}
may indicate that the hierarchy is wrong. Inheritance works best when every child can meaningfully satisfy the parent contract.
Complete Example
ContentItem.java
public class ContentItem {
private final long id;
private final String title;
private boolean published;
public ContentItem(long id, String title) {
if (id <= 0) {
throw new IllegalArgumentException("Content ID must be positive.");
}
if (title == null || title.isBlank()) {
throw new IllegalArgumentException("Content title is required.");
}
this.id = id;
this.title = title.strip();
this.published = false;
}
public int calculateEstimatedMinutes() {
return 0;
}
public String getContentType() {
return "CONTENT";
}
public boolean publish() {
if (published) {
return false;
}
published = true;
return true;
}
public final long getId() {
return id;
}
public final String getTitle() {
return title;
}
public final boolean isPublished() {
return published;
}
}
VideoLesson.java
public class VideoLesson extends ContentItem {
private final String videoUrl;
private final int durationInMinutes;
public VideoLesson(
long id, String title, String videoUrl, int durationInMinutes) {
super(id, title);
if (videoUrl == null || videoUrl.isBlank()) {
throw new IllegalArgumentException("Video URL is required.");
}
if (durationInMinutes <= 0) {
throw new IllegalArgumentException(
"Video duration must be positive.");
}
this.videoUrl = videoUrl.strip();
this.durationInMinutes = durationInMinutes;
}
@Override
public int calculateEstimatedMinutes() {
return durationInMinutes;
}
@Override
public String getContentType() {
return "VIDEO";
}
public String getVideoUrl() {
return videoUrl;
}
}
ArticleLesson.java
public class ArticleLesson extends ContentItem {
private static final int WORDS_PER_MINUTE = 200;
private final int wordCount;
public ArticleLesson(long id, String title, int wordCount) {
super(id, title);
if (wordCount <= 0) {
throw new IllegalArgumentException("Word count must be positive.");
}
this.wordCount = wordCount;
}
@Override
public int calculateEstimatedMinutes() {
int estimatedMinutes = wordCount / WORDS_PER_MINUTE;
return Math.max(1, estimatedMinutes);
}
@Override
public String getContentType() {
return "ARTICLE";
}
}
QuizLesson.java
public class QuizLesson extends ContentItem {
private static final int MINUTES_PER_QUESTION = 2;
private final int questionCount;
public QuizLesson(long id, String title, int questionCount) {
super(id, title);
if (questionCount <= 0) {
throw new IllegalArgumentException(
"Question count must be positive.");
}
this.questionCount = questionCount;
}
@Override
public int calculateEstimatedMinutes() {
return questionCount * MINUTES_PER_QUESTION;
}
@Override
public String getContentType() {
return "QUIZ";
}
}
Main.java
public class Main {
public static void main(String[] args) {
ContentItem video = new VideoLesson(1L, "Method Overriding",
"https://cdn.tarkev.com/videos/overriding", 18);
ContentItem article =
new ArticleLesson(2L, "Runtime Polymorphism", 1_200);
ContentItem quiz = new QuizLesson(3L, "Overriding Assessment", 10);
printEstimate(video);
printEstimate(article);
printEstimate(quiz);
}
private static void printEstimate(ContentItem content) {
System.out.println("Type: " + content.getContentType());
System.out.println("Title: " + content.getTitle());
System.out.println(
"Estimated time: "
+ content.calculateEstimatedMinutes() + " minutes");
System.out.println();
}
}
Output:
Type: VIDEO
Title: Method Overriding
Estimated time: 18 minutes
Type: ARTICLE
Title: Runtime Polymorphism
Estimated time: 6 minutes
Type: QUIZ
Title: Overriding Assessment
Estimated time: 20 minutes
Notice that printEstimate(ContentItem content) does not know whether the object is a VideoLesson, an ArticleLesson or a QuizLesson. Runtime dispatch selects the appropriate child behavior.
Important Terms
Method Overriding
A child class providing a compatible implementation of a parent instance method.
@Override
A compiler annotation indicating an intended override.
Runtime Polymorphism
Different child behavior executing through the same parent contract.
Runtime Dispatch
Selecting an overridden instance method according to the actual runtime object.
super.method()
Explicitly calls the parent implementation.
Predict the Result
Question 1
ContentItem content = new VideoLesson(1L, "Overriding",
"https://example.com/video", 15);
System.out.println(content.calculateEstimatedMinutes());
What prints?
Answer
15
The runtime object is VideoLesson.
Question 2
Parent:
public String getType() {
return "CONTENT";
}
Child:
@Override
public String getType() {
return "VIDEO";
}
Then:
ContentItem content = new VideoLesson(...);
System.out.println(content.getType());
What prints?
Answer
VIDEO
Instance-method runtime dispatch selects the child override.
Question 3
Can a child override public final void publish()?
Answer
No. A final method cannot be overridden.
Question 4
Parent:
private void validate() {
}
Child declares:
private void validate() {
}
Is this overriding?
Answer
No. The parent private method is not visible to the child.
Common Mistakes
Forgetting @Override
Without it, a misspelled name such as calculateEstimateMinutes() compiles as a new method, and the child silently keeps the parent's return 0 (see "The @Override Annotation"). With @Override, the compiler reports the mistake.
Changing the Parameters
calculateEstimatedMinutes(int speed) in the child does not override calculateEstimatedMinutes(); it is an overload. Calls through a ContentItem reference still run the parent's version.
Expecting Static Methods or Fields to Be Polymorphic
With ContentItem content = new VideoLesson(...), content.getCategory() (static) returns "CONTENT" and content.type reads the parent's field. Use an overridden instance method such as getContentType() when behavior should depend on the object.
Breaking the Parent's Contract
An override can compile and still be wrong: a negative estimate, or an estimate that also publishes the content. Callers rely on the parent's meaning, so keep it (see "Parent Contract Must Still Hold").
Practice Exercise 1: Quiz Duration
Solutions: lesson 4.10.
QuizLesson should override calculateEstimatedMinutes() with the rule: 3 minutes per question.
Practice Exercise 2: Content Type
Override the parent's getContentType(), which returns "CONTENT":
VideoLesson → VIDEO
ArticleLesson → ARTICLE
QuizLesson → QUIZ
Practice Exercise 3: Parent Reference
ContentItem content = new ArticleLesson(1L, "Polymorphism", 800);
content.calculateEstimatedMinutes();
Which implementation executes for the second line? Explain why.
Practice Exercise 4: Override or Overload?
Parent:
public void process(String value) {
}
Child A:
@Override
public void process(String value) {
}
Child B:
public void process(int value) {
}
Classify each.
Practice Exercise 5: Fix the Visibility
Fix the protected void publish() override from "Visibility Cannot Be Reduced" and explain why the fix is needed.
Practice Exercise 6: Method Instead of Field
Replace the field public String type = "CONTENT"; with a polymorphic getContentType() method.
Practice Exercise 7: Contract Review
The parent promises:
calculateEstimatedMinutes()
returns a non-negative value
Review:
@Override
public int calculateEstimatedMinutes() {
return -1;
}
Why is the signature valid but the design wrong?
Knowledge Check
Question 1
What is method overriding?
Question 2
What must the parameter list of an overriding method look like?
Question 3
Why is @Override useful?
Question 4
What is runtime method dispatch?
Question 5
Can a parent reference execute a child override?
Question 6
What does super.method() do?
Question 7
What is the difference between overriding and overloading?
Question 8
Can a child reduce the visibility of a public parent method?
Question 9
Are private methods overridden?
Question 10
Can a final method be overridden?
Question 11
Are static methods runtime polymorphic?
Question 12
Are fields runtime polymorphic?
Question 13
Does a correct method signature guarantee correct domain behavior?
Question 14
Why is preserving the parent contract important?
Knowledge Check Answers
Answer 1
When a child provides a compatible new implementation of a parent instance method with the same parameter signature.
Answer 2
It must match the parent method's parameter list.
Answer 3
The compiler verifies that the intended method really overrides another method.
Answer 4
Selecting the overridden instance method according to the actual runtime object type.
Answer 5
Yes.
Answer 6
It explicitly calls the parent implementation.
Answer 7
Overriding specializes inherited behavior and is selected at runtime. Overloading creates method variations with different parameter lists and is selected at compile time.
Answer 8
No.
Answer 9
No.
Answer 10
No.
Answer 11
No. Static behavior is class-level. A child static method with the same signature hides the parent's method; it does not override it.
Answer 12
No. Runtime polymorphism applies to instance-method behavior.
Answer 13
No. The compiler does not verify domain meaning.
Answer 14
So that code expecting the parent type can still work meaningfully and predictably with child objects.
Lesson Summary
- Method overriding lets a child replace or extend an inherited instance method with the same parameter list and a compatible return type;
@Overridemakes the compiler check it. - Runtime dispatch runs the implementation of the actual object, so one call through a parent reference behaves differently per child. That is runtime polymorphism, and it frees callers from type checks.
super.method()reuses the parent implementation inside an override.- Overriding is selected at runtime; overloading (different parameters) at compile time.
- An override cannot narrow visibility. Private,
finaland static methods, constructors and fields are not overridden. - A correct signature does not guarantee correct behavior: an override must keep the meaning of the parent operation.
আগে ভাবুন
শীঘ্রই আসছেCheckpoint প্রশ্ন, যেখানে lesson উত্তর দেখানোর আগে আপনাকে একটি উত্তর বেছে নিতে হবে।