Showing posts with label Java OOP. Show all posts
Showing posts with label Java OOP. Show all posts

Monday, December 8, 2025

Java Method Overriding: 3 Counter-Intuitive Rules You Need to Know

 

Introduction

Method overriding in Java seems straightforward at first glance. You have a method in a parent class, and you create a more specific version in a child class. Simple, right? But lurking beneath this simple definition are hidden complexities and "gotcha" rules that can easily trip up even experienced developers.

This post will distill the most surprising and impactful rules of method overriding. We won't just cover what the rules are; we'll explore the crucial why behind them, giving you a deeper, more practical understanding of how Java's object-oriented system is designed for stability and predictability.

--------------------------------------------------------------------------------

The Takeaways

1. The Golden Rule: Don't Break Your Promises to the Outside World

When overriding a method, the access modifier in the child class cannot be more restrictive than in the parent class. You can only keep the scope the same or increase it (e.g., from default to public), but you can never reduce it (e.g., from public to default).

The core rationale for this rule is to maintain a stable contract with other parts of your code that use the method. Imagine hundreds of pieces of code across your application rely on a public parent method. If a child class could override that method and make it default, all that external code would suddenly break because it would no longer have access. The rule prevents this chaos.

without overriding this method accessed by several outside people after overriding this method also should be accessed by same set of people... the people who are already accessing they should not be affected.

2. The Exception Rule: You Can't Throw New Problems at Your Callers

If a child's overriding method throws a checked exception, the parent's method must declare that it throws the same checked exception or a parent of that exception. A child method cannot introduce a completely new, undeclared checked exception.

This rule connects directly back to the principle of not breaking contracts. Code that calls the parent method is only prepared to try...catch the specific checked exceptions declared in the parent's method signature. The caller has a try-catch block designed for the parent's declared exceptions; forcing it to handle a new, unexpected checked exception from a child would break its compile-time safety contract.

It's important to note that this strict rule does not apply to unchecked exceptions (like RuntimeException and its children), as "there are no restrictions for unchecked exceptions".

if a child class method throws any checked exception compulsory parent class method should throw the same checked exception or its parent.

3. The Biggest Twist: Static Methods Can't Be Overridden

This is perhaps the most surprising rule: static methods cannot be overridden. Attempting to override a static method with a non-static one, or vice-versa, will result in a compile-time error.

So what happens if both the parent and child class methods are static? The code will compile without error, but this is not method overriding. The correct term for this behavior is Method Hiding.

The critical difference lies in how the method call is resolved:

  • Overriding (for non-static methods): Method resolution is handled by the JVM at runtime, based on the actual object's type. This is runtime polymorphism.
  • Method Hiding (for static methods): Method resolution is handled by the compiler at compile-time, based on the reference variable's type. This is compile-time polymorphism.

Consider the code parent p1 = new child(); p1.M1();. If M1 is a non-static method, overriding ensures the child's version is called. However, if M1 is a static method (method hiding), the parent's version is called because the compiler only sees the parent reference type.

To understand why the resolution behaves this way, the source offers a powerful analogy. Think of overriding as erasing something from a whiteboard and writing new data—the old data is truly gone. In contrast, think of method hiding as pinning a new chart over an old one. The new chart hides the old one, but both still exist. If you use a reference to the "new chart" (the child type), you see it. If you use a reference to the "old chart" (the parent type), you see that one instead.

it seems overriding concept applicable for static methods but it is not overriding and it is Method hiding.

--------------------------------------------------------------------------------

Conclusion

These rules, while sometimes complex, are not arbitrary. They are carefully designed to create predictable, stable, and less error-prone object-oriented systems. By enforcing clear contracts regarding access, exceptions, and method resolution, Java ensures that polymorphism works as intended without causing unexpected side effects in unrelated parts of an application.

Knowing that static methods are resolved by reference type at compile-time, how might this change your approach to designing and using utility classes in your projects?

Saturday, December 6, 2025

4 Surprising Rules About Java Method Overriding You Might Not Know

 

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 a marry() method exists in the Parent class. If it does, the code compiles successfully.
  • JVM's Role: At runtime, the JVM looks at the actual object that p1 is pointing to, which is a Child object. It then checks if the Child class has an overridden version of the marry() 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?

Featured Post

How LLMs Really Work: The Power of Predicting One Word at a Time

  1.0 Introduction: The Intelligence Illusion The most profound misconception about modern AI is that it understands . While models like Cha...

Popular Posts