Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
29: Method Overriding vs Overloading
I want to show you a snippet of code that looks perfectly fine at first glance. I once reviewed a PR from a junior dev who spent three hours wondering why their "specialized" logic wasn't firing. They were convinced the JVM was broken. It wasn't; they had just fallen into the classic trap of confusing overriding with overloading.
class PaymentProcessor {
void processPayment(double amount) {
System.out.println("Processing generic payment of $" + amount);
}
}
class CreditCardProcessor extends PaymentProcessor {
// The dev intended to override the base method here
void processPayment(int amount) {
System.out.println("Processing credit card payment of $" + amount + " with 2% fee");
}
}
public class Main {
public static void main(String[] args) {
PaymentProcessor processor = new CreditCardProcessor();
processor.processPayment(100.0);
}
}
If you run this, you'll see "Processing generic payment of $100.0". The developer expected the credit card logic to run, but Java ignored the method in CreditCardProcessor entirely. Why?
The Signature Mismatch Trap
Here is the problem: the developer didn't override the method; they overloaded it. Overriding requires the method signature (name and parameter types) to be identical. In the base class, we have a double. In the subclass, the dev used an int.
To Java, processPayment(double) and processPayment(int) are two completely different methods that just happen to share a name. Because the variable processor is declared as a PaymentProcessor, Java looks for a method that takes a double. It finds one in the base class and uses it. The int version in the subclass just sits there, unused and invisible, because it's treated as a brand new method specific to credit cards.
Forcing the Compiler to Help You
The fix is simple, but the habit is what matters. You should almost always use the @Override annotation. If the dev had written @Override above the method in CreditCardProcessor, the code wouldn't have even compiled. The compiler would have screamed: "Method does not override a method from its superclass."
class CreditCardProcessor extends PaymentProcessor {
@Override
void processPayment(double amount) { // Changed int to double
System.out.println("Processing credit card payment of $" + amount + " with 2% fee");
}
}
Now, when you call processor.processPayment(100.0), Java sees that the CreditCardProcessor has provided a specific implementation for that exact signature and uses it. This is polymorphism in action.
Knowing Which Tool to Reach For
I often see people use these interchangeably in conversation, but in your code, they serve opposite purposes. Think of it this way:
- Overloading is about flexibility. You use it when you want the same action to work with different types of input. For example, a
Loggerclass might havelog(String message)andlog(Exception e). Same name, different data, same class. - Overriding is about specialization. You use it when a subclass needs to do something differently than its parent. A
Dogclass overrides themakeSound()method of theAnimalclass to bark instead of making a generic noise.
Just remember: Overloading happens at compile-time (the compiler decides which version to call based on the arguments). Overriding happens at runtime (Java looks at the actual object type on the heap to decide which version to call).
📋 Practical Task
Implementing a Tiered Discount System
You are building a retail system where different customer types get different discount calculations. Your task is to implement a system that uses both overriding and overloading correctly.
Requirements:
- Create a base class
Customerwith a methodcalculateDiscount(double price)that returns a 5% discount (price * 0.05). - Create a subclass
VIPCustomerthat overridescalculateDiscount(double price)to provide a 20% discount. - In the
VIPCustomerclass, overload thecalculateDiscountmethod by creating a version that accepts(double price, int loyaltyYears). If loyaltyYears is greater than 5, the discount should be 30%; otherwise, it stays at 20%. - In your
mainmethod, instantiate aVIPCustomerbut store it in aCustomerreference. Call the standard discount method to verify overriding works. Then, cast it to aVIPCustomerto call the overloaded loyalty method.
There are no comments for now.