Method Overriding Rules in Java

Introduction

Imagine you have a generic recipe for making coffee, but you want to create a special version with extra ingredients while keeping the same basic process. That's exactly what method overriding does in Java! It allows a subclass to provide a specific implementation of a method that's already defined in its superclass.

Method overriding is like customizing inherited behavior—you keep the method signature but change what the method actually does to fit the subclass's specific needs.


What is Method Overriding?

Method overriding occurs when a subclass provides a specific implementation for a method that is already defined in its parent class. The overridden method in the subclass must have the same signature as the method in the parent class.

Key Characteristics:

  • 🔄 Runtime polymorphism: Method resolution happens at runtime
  • 🎯 Same signature: Method name, parameters, and return type must match
  • 📈 IS-A relationship: Only possible through inheritance
  • 🚫 Access cannot be restricted: Can't reduce visibility
  • Covariant return types: Allowed in specific cases

Code Explanation with Examples

Example 1: Basic Method Overriding

public class BasicOverriding {
public static void main(String[] args) {
System.out.println("=== BASIC METHOD OVERRIDING ===");
// Creating objects
Animal genericAnimal = new Animal();
Dog myDog = new Dog();
Cat myCat = new Cat();
// Calling overridden methods
System.out.println("Animal sound: " + genericAnimal.makeSound());
System.out.println("Dog sound: " + myDog.makeSound());
System.out.println("Cat sound: " + myCat.makeSound());
// Polymorphism in action
System.out.println("\n=== POLYMORPHISM DEMONSTRATION ===");
Animal animal1 = new Dog();  // Animal reference, Dog object
Animal animal2 = new Cat();  // Animal reference, Cat object
System.out.println("Animal1 sound: " + animal1.makeSound()); // Woof! Woof!
System.out.println("Animal2 sound: " + animal2.makeSound()); // Meow! Meow!
// Method with parameters overriding
System.out.println("\n=== METHOD WITH PARAMETERS ===");
System.out.println("Animal move: " + genericAnimal.move(5));
System.out.println("Dog move: " + myDog.move(5));
System.out.println("Cat move: " + myCat.move(5));
}
}
class Animal {
// Method to be overridden
public String makeSound() {
return "Some generic animal sound";
}
public String move(int distance) {
return "Animal moves " + distance + " meters";
}
// Method that won't be overridden
public void breathe() {
System.out.println("Animal is breathing...");
}
}
class Dog extends Animal {
// Overriding makeSound method
@Override
public String makeSound() {
return "Woof! Woof!";
}
// Overriding move method with specific behavior
@Override
public String move(int distance) {
return "Dog runs " + distance + " meters happily";
}
// Additional method specific to Dog
public void fetch() {
System.out.println("Dog is fetching the ball!");
}
}
class Cat extends Animal {
// Overriding makeSound method
@Override
public String makeSound() {
return "Meow! Meow!";
}
// Overriding move method with specific behavior
@Override
public String move(int distance) {
return "Cat gracefully moves " + distance + " meters";
}
// Additional method specific to Cat
public void climb() {
System.out.println("Cat is climbing the tree!");
}
}

Output:

=== BASIC METHOD OVERRIDING ===
Animal sound: Some generic animal sound
Dog sound: Woof! Woof!
Cat sound: Meow! Meow!
=== POLYMORPHISM DEMONSTRATION ===
Animal1 sound: Woof! Woof!
Animal2 sound: Meow! Meow!
=== METHOD WITH PARAMETERS ===
Animal move: Animal moves 5 meters
Dog move: Dog runs 5 meters happily
Cat move: Cat gracefully moves 5 meters

Example 2: Overriding Rules and @Override Annotation

public class OverridingRules {
public static void main(String[] args) {
System.out.println("=== METHOD OVERRIDING RULES ===");
// Testing different scenarios
Parent parent = new Parent();
Child child = new Child();
System.out.println("Parent method: " + parent.doSomething());
System.out.println("Child method: " + child.doSomething());
// Access modifier rules
System.out.println("\n=== ACCESS MODIFIER RULES ===");
System.out.println("Parent protected: " + parent.protectedMethod());
System.out.println("Child protected: " + child.protectedMethod());
// Return type rules
System.out.println("\n=== RETURN TYPE RULES ===");
Parent pRef = new Child();
System.out.println("Covariant return: " + pRef.getObject());
// Exception handling rules
System.out.println("\n=== EXCEPTION HANDLING RULES ===");
try {
child.methodWithException();
} catch (Exception e) {
System.out.println("Caught exception: " + e.getMessage());
}
// Final method demonstration
System.out.println("\n=== FINAL METHODS ===");
System.out.println("Parent final: " + parent.finalMethod());
System.out.println("Child final: " + child.finalMethod()); // Inherited, not overridden
}
}
class Parent {
// Regular method - can be overridden
public String doSomething() {
return "Parent doing something";
}
// Protected method - can be overridden with same or broader access
protected String protectedMethod() {
return "Parent protected method";
}
// Method with return type - can use covariant return type
public Object getObject() {
return new Object();
}
// Method that throws exception
public void methodWithException() throws Exception {
throw new Exception("Exception from Parent");
}
// Final method - cannot be overridden
public final String finalMethod() {
return "This is final in Parent";
}
// Private method - not inherited, so cannot be overridden
private String privateMethod() {
return "Private method in Parent";
}
// Static method - not overridden, but can be hidden
public static String staticMethod() {
return "Static method in Parent";
}
}
class Child extends Parent {
// ✅ CORRECT: Same signature, public access (same as parent)
@Override
public String doSomething() {
return "Child doing something differently";
}
// ✅ CORRECT: Can change protected to public (broader access)
@Override
public String protectedMethod() {
return "Child protected method - now public";
}
// ✅ CORRECT: Covariant return type (String is subclass of Object)
@Override
public String getObject() {
return "Child returning String instead of Object";
}
// ✅ CORRECT: Can throw same exception or subclass exception
@Override
public void methodWithException() throws RuntimeException {
throw new RuntimeException("RuntimeException from Child");
}
// ❌ WRONG: Cannot reduce visibility (commented out to avoid error)
/*
@Override
private String doSomething() {  // COMPILER ERROR: cannot reduce visibility
return "This won't compile";
}
*/
// ❌ WRONG: Cannot override final method (commented out to avoid error)
/*
@Override
public final String finalMethod() {  // COMPILER ERROR: cannot override final
return "This won't compile";
}
*/
// This is NOT overriding - it's method hiding (different from overriding)
public static String staticMethod() {
return "Static method in Child - this is method hiding, not overriding";
}
// This is NOT overriding - private methods are not inherited
private String privateMethod() {
return "This is a new method in Child, not overriding";
}
}
// Demonstration of abstract method overriding
abstract class Shape {
// Abstract method - must be overridden by concrete subclasses
public abstract double calculateArea();
// Concrete method - can be overridden
public String getDescription() {
return "This is a shape";
}
}
class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
// ✅ MUST override abstract method
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
// ✅ CAN override concrete method
@Override
public String getDescription() {
return "This is a circle with radius " + radius;
}
}
class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
// ✅ MUST override abstract method
@Override
public double calculateArea() {
return width * height;
}
// Choosing NOT to override getDescription() - uses parent's version
}

Output:

=== METHOD OVERRIDING RULES ===
Parent method: Parent doing something
Child method: Child doing something differently
=== ACCESS MODIFIER RULES ===
Parent protected: Parent protected method
Child protected: Child protected method - now public
=== RETURN TYPE RULES ===
Covariant return: Child returning String instead of Object
=== EXCEPTION HANDLING RULES ===
Caught exception: RuntimeException from Child
=== FINAL METHODS ===
Parent final: This is final in Parent
Child final: This is final in Parent

Example 3: Real-World Overriding Examples

public class RealWorldOverriding {
public static void main(String[] args) {
System.out.println("=== REAL-WORLD OVERRIDING EXAMPLES ===");
// Payment processing system
System.out.println("\n=== PAYMENT PROCESSING ===");
PaymentProcessor creditCard = new CreditCardProcessor();
PaymentProcessor paypal = new PayPalProcessor();
PaymentProcessor crypto = new CryptoProcessor();
processPayment(creditCard, 100.0);
processPayment(paypal, 150.0);
processPayment(crypto, 200.0);
// Employee management system
System.out.println("\n=== EMPLOYEE MANAGEMENT ===");
Employee manager = new Manager("Alice", 75000, 10000);
Employee developer = new Developer("Bob", 60000, 5);
Employee intern = new Intern("Charlie", 30000);
displayEmployeeInfo(manager);
displayEmployeeInfo(developer);
displayEmployeeInfo(intern);
// Notification system
System.out.println("\n=== NOTIFICATION SYSTEM ===");
Notification email = new EmailNotification();
Notification sms = new SMSNotification();
Notification push = new PushNotification();
sendNotification(email, "Hello via Email!");
sendNotification(sms, "Hello via SMS!");
sendNotification(push, "Hello via Push!");
}
// Polymorphic method - works with any PaymentProcessor
public static void processPayment(PaymentProcessor processor, double amount) {
System.out.println("Processing $" + amount + " payment...");
processor.processPayment(amount);
System.out.println("Fee: $" + processor.calculateFee(amount));
System.out.println();
}
// Polymorphic method - works with any Employee
public static void displayEmployeeInfo(Employee employee) {
System.out.println("Name: " + employee.getName());
System.out.println("Base Salary: $" + employee.getBaseSalary());
System.out.println("Total Salary: $" + employee.calculateSalary());
System.out.println("Role: " + employee.getRole());
System.out.println();
}
// Polymorphic method - works with any Notification
public static void sendNotification(Notification notification, String message) {
notification.send(message);
System.out.println();
}
}
// Payment Processing System
abstract class PaymentProcessor {
protected String processorName;
public PaymentProcessor(String name) {
this.processorName = name;
}
// Abstract method - must be overridden
public abstract void processPayment(double amount);
// Concrete method - can be overridden
public double calculateFee(double amount) {
return amount * 0.02; // 2% default fee
}
// Final method - cannot be overridden
public final String getProcessorName() {
return processorName;
}
}
class CreditCardProcessor extends PaymentProcessor {
public CreditCardProcessor() {
super("Credit Card");
}
@Override
public void processPayment(double amount) {
System.out.println("Processing credit card payment of $" + amount);
System.out.println("Validating card details...");
System.out.println("Authorizing transaction...");
System.out.println("Payment completed successfully!");
}
@Override
public double calculateFee(double amount) {
return amount * 0.025; // 2.5% fee for credit cards
}
}
class PayPalProcessor extends PaymentProcessor {
public PayPalProcessor() {
super("PayPal");
}
@Override
public void processPayment(double amount) {
System.out.println("Processing PayPal payment of $" + amount);
System.out.println("Redirecting to PayPal...");
System.out.println("Confirming payment...");
System.out.println("Payment completed successfully!");
}
@Override
public double calculateFee(double amount) {
return amount * 0.029 + 0.30; // 2.9% + $0.30 fixed fee
}
}
class CryptoProcessor extends PaymentProcessor {
public CryptoProcessor() {
super("Cryptocurrency");
}
@Override
public void processPayment(double amount) {
System.out.println("Processing cryptocurrency payment of $" + amount);
System.out.println("Converting to BTC...");
System.out.println("Confirming blockchain transaction...");
System.out.println("Payment completed successfully!");
}
@Override
public double calculateFee(double amount) {
return amount * 0.01; // 1% fee for crypto (lower fees)
}
}
// Employee Management System
class Employee {
protected String name;
protected double baseSalary;
public Employee(String name, double baseSalary) {
this.name = name;
this.baseSalary = baseSalary;
}
public String getName() {
return name;
}
public double getBaseSalary() {
return baseSalary;
}
// Method to be overridden by subclasses
public double calculateSalary() {
return baseSalary;
}
// Method to be overridden by subclasses
public String getRole() {
return "Employee";
}
}
class Manager extends Employee {
private double bonus;
public Manager(String name, double baseSalary, double bonus) {
super(name, baseSalary);
this.bonus = bonus;
}
@Override
public double calculateSalary() {
return baseSalary + bonus; // Salary includes bonus
}
@Override
public String getRole() {
return "Manager";
}
// Additional method specific to Manager
public void conductMeeting() {
System.out.println(name + " is conducting a team meeting");
}
}
class Developer extends Employee {
private int yearsOfExperience;
public Developer(String name, double baseSalary, int yearsOfExperience) {
super(name, baseSalary);
this.yearsOfExperience = yearsOfExperience;
}
@Override
public double calculateSalary() {
// Developers get experience-based bonus
double experienceBonus = baseSalary * (yearsOfExperience * 0.05);
return baseSalary + experienceBonus;
}
@Override
public String getRole() {
return "Developer with " + yearsOfExperience + " years experience";
}
// Additional method specific to Developer
public void writeCode() {
System.out.println(name + " is writing code");
}
}
class Intern extends Employee {
public Intern(String name, double baseSalary) {
super(name, baseSalary);
}
@Override
public String getRole() {
return "Intern";
}
// Interns don't override calculateSalary() - they use the base version
}
// Notification System
interface Notification {
void send(String message);
String getType();
}
class EmailNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending Email Notification:");
System.out.println("From: [email protected]");
System.out.println("To: [email protected]");
System.out.println("Subject: Notification");
System.out.println("Body: " + message);
System.out.println("Email sent successfully!");
}
@Override
public String getType() {
return "Email";
}
}
class SMSNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending SMS Notification:");
System.out.println("To: +1234567890");
System.out.println("Message: " + message);
System.out.println("SMS sent successfully!");
}
@Override
public String getType() {
return "SMS";
}
}
class PushNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending Push Notification:");
System.out.println("Device: User's Smartphone");
System.out.println("Title: App Notification");
System.out.println("Body: " + message);
System.out.println("Push notification sent successfully!");
}
@Override
public String getType() {
return "Push";
}
}

Output:

=== REAL-WORLD OVERRIDING EXAMPLES ===
=== PAYMENT PROCESSING ===
Processing $100.0 payment...
Processing credit card payment of $100.0
Validating card details...
Authorizing transaction...
Payment completed successfully!
Fee: $2.5
Processing $150.0 payment...
Processing PayPal payment of $150.0
Redirecting to PayPal...
Confirming payment...
Payment completed successfully!
Fee: $4.65
Processing $200.0 payment...
Processing cryptocurrency payment of $200.0
Converting to BTC...
Confirming blockchain transaction...
Payment completed successfully!
Fee: $2.0
=== EMPLOYEE MANAGEMENT ===
Name: Alice
Base Salary: $75000.0
Total Salary: $85000.0
Role: Manager
Name: Bob
Base Salary: $60000.0
Total Salary: $75000.0
Role: Developer with 5 years experience
Name: Charlie
Base Salary: $30000.0
Total Salary: $30000.0
Role: Intern
=== NOTIFICATION SYSTEM ===
Sending Email Notification:
From: [email protected]
To: [email protected]
Subject: Notification
Body: Hello via Email!
Email sent successfully!
Sending SMS Notification:
To: +1234567890
Message: Hello via SMS!
SMS sent successfully!
Sending Push Notification:
Device: User's Smartphone
Title: App Notification
Body: Hello via Push!
Push notification sent successfully!

Example 4: Advanced Overriding Concepts

public class AdvancedOverriding {
public static void main(String[] args) {
System.out.println("=== ADVANCED OVERRIDING CONCEPTS ===");
// Covariant return types
System.out.println("=== COVARIANT RETURN TYPES ===");
AnimalFactory animalFactory = new DogFactory();
Animal animal = animalFactory.createAnimal();
System.out.println("Created: " + animal.getClass().getSimpleName());
// Exception handling in overriding
System.out.println("\n=== EXCEPTION HANDLING ===");
DataReader fileReader = new FileDataReader();
DataReader networkReader = new NetworkDataReader();
try {
fileReader.readData();
} catch (DataReadException e) {
System.out.println("File read error: " + e.getMessage());
}
try {
networkReader.readData();
} catch (DataReadException e) {
System.out.println("Network read error: " + e.getMessage());
}
// Using super in overriding
System.out.println("\n=== USING SUPER IN OVERRIDING ===");
AdvancedChild child = new AdvancedChild();
child.display();
// Method hiding vs overriding
System.out.println("\n=== METHOD HIDING VS OVERRIDING ===");
ParentClass parentRef = new ChildClass();
ChildClass childRef = new ChildClass();
// Instance method - overriding (runtime polymorphism)
parentRef.instanceMethod();  // Child's version (overriding)
childRef.instanceMethod();   // Child's version
// Static method - hiding (compile-time polymorphism)
parentRef.staticMethod();    // Parent's version (hiding)
childRef.staticMethod();     // Child's version
ParentClass.staticMethod();  // Parent's version
ChildClass.staticMethod();   // Child's version
}
}
// Covariant return type example
class Animal {
public String getName() {
return "Generic Animal";
}
}
class Dog extends Animal {
@Override
public String getName() {
return "Dog";
}
public void bark() {
System.out.println("Woof! Woof!");
}
}
class AnimalFactory {
public Animal createAnimal() {
return new Animal();
}
}
class DogFactory extends AnimalFactory {
// ✅ Covariant return type - returning Dog instead of Animal
@Override
public Dog createAnimal() {
return new Dog();
}
}
// Exception handling in overriding
class DataReadException extends Exception {
public DataReadException(String message) {
super(message);
}
}
class DataReader {
public void readData() throws DataReadException {
throw new DataReadException("Generic data read error");
}
// Method that throws multiple exceptions
public void processData() throws IOException, SQLException {
// Some processing
}
}
class FileDataReader extends DataReader {
@Override
public void readData() throws DataReadException {
throw new DataReadException("File not found or corrupted");
}
// ✅ Can throw same exceptions
@Override
public void processData() throws IOException, SQLException {
// File processing
}
}
class NetworkDataReader extends DataReader {
@Override
public void readData() throws DataReadException {
throw new DataReadException("Network connection failed");
}
// ✅ Can throw subclass exceptions
@Override
public void processData() throws FileNotFoundException, SQLException {
// Network processing - FileNotFoundException is subclass of IOException
}
// ❌ Cannot throw broader exceptions (commented out to avoid error)
/*
@Override
public void processData() throws Exception {  // Exception is broader than IOException
// This won't compile
}
*/
}
// Using super in overriding
class AdvancedParent {
protected String value = "Parent Value";
public void display() {
System.out.println("Parent display: " + value);
}
public void showMessage() {
System.out.println("Message from Parent");
}
}
class AdvancedChild extends AdvancedParent {
private String value = "Child Value";
@Override
public void display() {
// Call parent's method using super
super.display();
// Then add child-specific behavior
System.out.println("Child display: " + this.value);
System.out.println("Parent's value via super: " + super.value);
}
@Override
public void showMessage() {
// Can choose when to call parent's method
System.out.println("Before calling parent's method");
super.showMessage();
System.out.println("After calling parent's method");
}
public void demonstrateSuper() {
// Using super to access parent's method
super.showMessage();
// Using this to access current class's method
this.showMessage();
}
}
// Method hiding vs overriding
class ParentClass {
// Instance method - can be overridden
public void instanceMethod() {
System.out.println("ParentClass instanceMethod");
}
// Static method - can be hidden, not overridden
public static void staticMethod() {
System.out.println("ParentClass staticMethod");
}
}
class ChildClass extends ParentClass {
// ✅ Overriding instance method
@Override
public void instanceMethod() {
System.out.println("ChildClass instanceMethod - OVERRIDDEN");
}
// ❌ This is method hiding, not overriding
public static void staticMethod() {
System.out.println("ChildClass staticMethod - HIDDEN");
}
// ❌ Cannot use @Override with static method (commented out to avoid error)
/*
@Override
public static void staticMethod() {  // COMPILER ERROR
System.out.println("This won't compile");
}
*/
}
// Interface method overriding
interface Vehicle {
default void start() {
System.out.println("Vehicle starting...");
}
void move();
}
class Car implements Vehicle {
// ✅ Can override default method
@Override
public void start() {
System.out.println("Car starting with key...");
}
// ✅ Must implement abstract method
@Override
public void move() {
System.out.println("Car moving on road...");
}
}
class ElectricCar extends Car {
// ✅ Can override again
@Override
public void start() {
System.out.println("Electric car starting silently...");
}
}

Output:

=== ADVANCED OVERRIDING CONCEPTS ===
=== COVARIANT RETURN TYPES ===
Created: Dog
=== EXCEPTION HANDLING ===
File read error: File not found or corrupted
Network read error: Network connection failed
=== USING SUPER IN OVERRIDING ===
Parent display: Parent Value
Child display: Child Value
Parent's value via super: Parent Value
=== METHOD HIDING VS OVERRIDING ===
ChildClass instanceMethod - OVERRIDDEN
ChildClass instanceMethod - OVERRIDDEN
ParentClass staticMethod
ChildClass staticMethod - HIDDEN
ParentClass staticMethod
ChildClass staticMethod - HIDDEN

Example 5: Common Pitfalls and Best Practices

public class OverridingPitfalls {
public static void main(String[] args) {
System.out.println("=== COMMON PITFALLS AND BEST PRACTICES ===");
// Pitfall 1: Accidental overloading instead of overriding
System.out.println("=== PITFALL 1: ACCIDENTAL OVERLOADING ===");
ProblematicChild problemChild = new ProblematicChild();
problemChild.doSomething("test");  // Calls child's method (overloading)
// To call parent's method, we need to cast
ProblematicParent problemParent = problemChild;
problemParent.doSomething();       // Calls parent's method
// Pitfall 2: Forgetting @Override annotation
System.out.println("\n=== PITFALL 2: MISSING @OVERRIDE ===");
MissingOverrideChild missingOverride = new MissingOverrideChild();
missingOverride.doSomething();     // Intended to override but doesn't!
// Best practices demonstration
System.out.println("\n=== BEST PRACTICES ===");
GoodChild goodChild = new GoodChild();
goodChild.properlyOverriddenMethod();
goodChild.useSuperAppropriately();
// Demonstration of proper equals and hashCode overriding
System.out.println("\n=== EQUALS AND HASHCODE ===");
Person person1 = new Person("Alice", 25);
Person person2 = new Person("Alice", 25);
Person person3 = new Person("Bob", 30);
System.out.println("person1.equals(person2): " + person1.equals(person2));
System.out.println("person1.equals(person3): " + person1.equals(person3));
System.out.println("person1 hashCode: " + person1.hashCode());
System.out.println("person2 hashCode: " + person2.hashCode());
System.out.println("person3 hashCode: " + person3.hashCode());
// toString overriding demonstration
System.out.println("\n=== TOSTRING OVERRIDING ===");
System.out.println("person1: " + person1);
System.out.println("person2: " + person2);
System.out.println("person3: " + person3);
}
}
// Pitfall 1: Accidental overloading
class ProblematicParent {
public void doSomething() {
System.out.println("Parent doSomething() - no parameters");
}
}
class ProblematicChild extends ProblematicParent {
// ❌ PITFALL: This is overloading, not overriding!
// Different signature (added parameter)
public void doSomething(String parameter) {
System.out.println("Child doSomething(String) - this is OVERLOADING, not overriding!");
}
// ✅ The correct overriding would be:
// @Override
// public void doSomething() {
//     System.out.println("Child doSomething() - properly overridden");
// }
}
// Pitfall 2: Forgetting @Override annotation
class MissingOverrideParent {
public void doSomething() {
System.out.println("Parent doSomething()");
}
}
class MissingOverrideChild extends MissingOverrideParent {
// ❌ PITFALL: Misspelled method name, but no compiler error without @Override
public void doSomethin() {  // Missing 'g' - should be doSomething
System.out.println("Child doSomethin() - this is a NEW method, not overriding!");
}
// ✅ With @Override, we'd get a compiler error for the misspelling:
/*
@Override
public void doSomethin() {  // COMPILER ERROR: method does not override
System.out.println("This won't compile with @Override annotation");
}
*/
}
// Best practices example
class GoodParent {
protected String data = "Parent Data";
public void properlyOverriddenMethod() {
System.out.println("GoodParent: properlyOverriddenMethod");
}
public void useSuperAppropriately() {
System.out.println("GoodParent: useSuperAppropriately");
}
// Method designed for extension
protected void templateMethod() {
System.out.println("GoodParent: Starting template method");
doCustomPart();
System.out.println("GoodParent: Ending template method");
}
// Hook method - meant to be overridden
protected void doCustomPart() {
System.out.println("GoodParent: Default custom part");
}
}
class GoodChild extends GoodParent {
// ✅ BEST PRACTICE: Always use @Override annotation
@Override
public void properlyOverriddenMethod() {
System.out.println("GoodChild: properlyOverriddenMethod - correctly overridden with @Override");
}
@Override
public void useSuperAppropriately() {
// ✅ BEST PRACTICE: Call super when it makes sense
super.useSuperAppropriately();
System.out.println("GoodChild: useSuperAppropriately - extending parent behavior");
}
@Override
protected void doCustomPart() {
// ✅ BEST PRACTICE: Override hook methods to customize behavior
System.out.println("GoodChild: Custom implementation of doCustomPart");
}
public void demonstrateTemplate() {
System.out.println("Demonstrating template method pattern:");
templateMethod();
}
}
// Proper equals and hashCode overriding
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// ✅ PROPER equals() OVERRIDING
@Override
public boolean equals(Object obj) {
// 1. Check if same object reference
if (this == obj) return true;
// 2. Check if null or different class
if (obj == null || this.getClass() != obj.getClass()) return false;
// 3. Cast to Person
Person other = (Person) obj;
// 4. Compare significant fields
return age == other.age && 
(name == null ? other.name == null : name.equals(other.name));
}
// ✅ PROPER hashCode() OVERRIDING
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + age;
return result;
}
// ✅ PROPER toString() OVERRIDING
@Override
public String toString() {
return String.format("Person{name='%s', age=%d}", name, age);
}
// ❌ COMMON MISTAKE: Overloading equals instead of overriding
public boolean equals(Person other) {  // This is OVERLOADING, not overriding!
if (other == null) return false;
return age == other.age && 
(name == null ? other.name == null : name.equals(other.name));
}
}
// Common pitfalls with access modifiers
class AccessModifierParent {
public void publicMethod() {
System.out.println("Parent public method");
}
protected void protectedMethod() {
System.out.println("Parent protected method");
}
}
class AccessModifierChild extends AccessModifierParent {
// ✅ Can keep public access
@Override
public void publicMethod() {
System.out.println("Child public method - same access");
}
// ✅ Can increase access (protected to public)
@Override
public void protectedMethod() {
System.out.println("Child protected method - now public");
}
// ❌ Cannot decrease access (commented out to avoid error)
/*
@Override
protected void publicMethod() {  // COMPILER ERROR: cannot reduce visibility
System.out.println("This won't compile");
}
*/
}
// Final class and methods
final class FinalClass {
public void normalMethod() {
System.out.println("Normal method in final class");
}
public final void finalMethod() {
System.out.println("Final method - cannot be overridden");
}
}
// ❌ Cannot extend final class (commented out to avoid error)
/*
class FinalClassChild extends FinalClass {  // COMPILER ERROR: cannot inherit from final
@Override
public void finalMethod() {  // This also won't compile
System.out.println("Cannot override final method");
}
}
*/

Output:

=== COMMON PITFALLS AND BEST PRACTICES ===
=== PITFALL 1: ACCIDENTAL OVERLOADING ===
Child doSomething(String) - this is OVERLOADING, not overriding!
Parent doSomething() - no parameters
=== PITFALL 2: MISSING @OVERRIDE ===
Child doSomethin() - this is a NEW method, not overriding!
=== BEST PRACTICES ===
GoodChild: properlyOverriddenMethod - correctly overridden with @Override
GoodParent: useSuperAppropriately
GoodChild: useSuperAppropriately - extending parent behavior
Demonstrating template method pattern:
GoodParent: Starting template method
GoodChild: Custom implementation of doCustomPart
GoodParent: Ending template method
=== EQUALS AND HASHCODE ===
person1.equals(person2): true
person1.equals(person3): false
person1 hashCode: 92959423
person2 hashCode: 92959423
person3 hashCode: 83460091
=== TOSTRING OVERRIDING ===
person1: Person{name='Alice', age=25}
person2: Person{name='Alice', age=25}
person3: Person{name='Bob', age=30}

Method Overriding Rules Summary

RuleDescriptionExample
Same SignatureMethod name, parameters, and return type must matchvoid method(int x)void method(int x)
IS-A RelationshipOnly through inheritanceChild extends Parent
Access ModifierCan't reduce visibilitypublicpublic (not private)
Final MethodsCannot override final methodspublic final void method()
Static MethodsCannot override, but can hideMethod hiding, not overriding
Covariant ReturnsCan return subclass of return typeAnimalDog
Exception HandlingCan throw same or subclass exceptionsIOExceptionFileNotFoundException

@Override Annotation Benefits

  1. Compiler checking: Ensures method actually overrides
  2. Readability: Clearly indicates overriding intent
  3. Maintenance: Prevents accidental overloading
  4. Documentation: Shows design decisions

Best Practices

✅ Do:

  • Always use @Override annotation
  • Call super.method() when extending behavior
  • Follow Liskov Substitution Principle
  • Override toString(), equals(), hashCode() properly
  • Use covariant return types when appropriate
  • Document behavior changes in overridden methods

❌ Don't:

  • Change method semantics when overriding
  • Forget to call super when it's needed
  • Override with completely different behavior
  • Ignore exception specifications
  • Override static methods (they're hidden, not overridden)

Common Pitfalls

  1. Accidental overloading instead of overriding
  2. Forgetting @Override annotation
  3. Changing method semantics unexpectedly
  4. Not calling super when parent behavior is needed
  5. Reducing access visibility
  6. Overriding with incompatible exceptions

Performance Considerations

  • Virtual method calls have slight overhead
  • JVM optimizations like inline caching help
  • Final methods can be inlined by JIT compiler
  • Interface method calls are optimized in modern JVMs

Conclusion

Method overriding is Java's mechanism for runtime polymorphism and behavior customization:

  • 🔄 Runtime binding: Method resolution at execution time
  • 🎯 Same signature: Maintains contract with superclass
  • 📈 IS-A relationship: Fundamental to inheritance
  • 🚫 Controlled changes: Rules prevent breaking changes
  • @Override safety: Compiler checks for correct overriding

Key Takeaways:

  • Use @Override always for compiler safety
  • Maintain method contracts when overriding
  • Call super.method() to preserve parent behavior
  • Follow access rules - don't reduce visibility
  • Handle exceptions properly - same or subclasses

Mastering method overriding is essential for creating flexible, maintainable object-oriented designs that leverage polymorphism effectively!

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper