When we first learn about inheritance in Java, method overriding seems straightforward. A parent class defines a method, but the child class isn't satisfied with the implementation and decides to provide its own. Much like a child might disagree with a parent's choice of who they should marry(), the child class can override the parent's logic with its own. But while the concept is simple, the rules governing it are full of surprising subtleties that can trip up even experienced developers.
Let's dive into four rules of method overriding that separate the experts from the novices—and see how they can make you a more effective Java programmer.
--------------------------------------------------------------------------------
1. It’s All Decided at Runtime (And That’s Why It’s Called Polymorphism)
The first crucial rule is that the decision of which overridden method to execute is handled by the JVM at runtime, not by the compiler. This distinction is the very foundation of polymorphism in Java.
Consider a common scenario where a parent reference holds a child object:
Parent p1 = new Child();
p1.marry(); // At runtime, the JVM executes the Child's version.
When you call a method like p1.marry(), the compiler and the JVM have distinct jobs:
- Compiler's Role: The compiler only looks at the reference type, which is
Parent. It checks if amarry()method exists in theParentclass. If it does, the code compiles successfully. - JVM's Role: At runtime, the JVM looks at the actual object that
p1is pointing to, which is aChildobject. It then checks if theChildclass has an overridden version of themarry()method. If it does, the child's method is executed.
This separation of concerns is a common source of confusion, but understanding it is key to mastering polymorphism and debugging unexpected runtime behavior. This runtime resolution is why overriding is a cornerstone of dynamic behavior in Java.
In overriding method resolution is always based on runtime object... that's why overriding is also known as runtime polymorphism or dynamic polymorphism or late binding.
While the JVM handles which method to run, the rules for what that method can return are surprisingly flexible.
--------------------------------------------------------------------------------
2. Return Types Don't Always Have to Be Identical (Welcome to Covariant Returns)
A common misconception is that the return type of an overriding method must be identical to the parent method's return type. While this was the strict rule until Java 1.4, the introduction of Java 1.5 brought more flexibility.
The modern rule introduces the concept of covariant return types. This means an overriding method in a child class can have a return type that is a subtype of the parent method's return type.
For example, if a parent method returns Object, a child can override it to return a String, because String is a subtype of Object. Crucially, this relationship is one-way. A method returning String cannot be overridden to return Object, as that would be broadening the return type, which is not allowed.
There's a critical limitation to remember: this flexibility only applies to object types, not primitives. If the parent method's return type is double, the overriding child method's return type must also be double.
...child class method return type need not be same as parent method return type it's child type also allowed.
Just as return types have rules, so do the access modifiers you can apply to an overridden method.
--------------------------------------------------------------------------------
3. You Can’t Weaken Access, But You Can Strengthen It
The rules for access modifiers in overriding are strict but logical: you cannot reduce the scope of the access modifier, but you can increase it.
For instance, if a method in the parent class is declared public, any overriding method in a child class must also be public. You cannot make it protected or default, as that would weaken its accessibility. If you try, the compiler will stop you with a clear error message:
attempting to assign weaker access privileges; was public
However, you are free to increase the scope. A protected method in a parent class can be overridden by a public method in a child class. The hierarchy of access is private -> default -> protected -> public. When overriding, you can only move from left to right in this hierarchy (e.g., protected to public), never from right to left.
Note that private methods are not included here because they are not inherited by child classes and therefore cannot be overridden at all.
This leads us to our final, and perhaps most surprising, rule.
--------------------------------------------------------------------------------
4. You Can Override a Concrete Method with an Abstract One
This last rule is perhaps the most counter-intuitive. It is perfectly valid in Java to override a non-abstract (fully implemented) method from a parent class with an abstract method in the child class.
From a design perspective, this is a powerful tool to control an inheritance hierarchy. But why would you ever want to do this? The primary reason is to force the next level of child classes to provide a completely new implementation. By declaring the method as abstract in the child, you effectively prevent any of its own subclasses (the "grandchildren") from inheriting and using the original implementation from the "grandparent" class.
...we can stop the availability of parent method implementation to the next level child classes.
Of course, the reverse is more common: an abstract method in a parent must be implemented by a child class. The only exception is if the child class is also declared abstract, which effectively pushes the implementation responsibility further down the inheritance chain.
--------------------------------------------------------------------------------
Conclusion
Method overriding is a powerful feature that enables polymorphism and flexible code design in Java. However, its power comes from a set of nuanced rules that go far beyond simply redefining a method. Understanding these details—from runtime method resolution and covariant returns to the strict rules on access modifiers—is a hallmark of a seasoned Java developer.
Which of these overriding rules has surprised you or caused a bug in your own code?
No comments:
Post a Comment