Method Overriding#
Lecture 18#
Java Programming (4343203)
Diploma in ICT - Semester IV
Gujarat Technological University
layout: default#
Learning Objectives#
By the end of this lecture, you will be able to:
- ๐ Understand method overriding vs method overloading
- ๐ฏ Implement method overriding with @Override annotation
- โก Apply dynamic method dispatch and runtime polymorphism
- ๐ก๏ธ Follow overriding rules and restrictions
- ๐จ Design polymorphic systems using method overriding
- ๐ Practice with real-world overriding scenarios
layout: center#
What is Method Overriding?#
graph TD
A[Method Overriding] --> B[Same Method Signature]
A --> C[Different Implementation]
A --> D[Runtime Decision]
B --> E[Same Name]
B --> F[Same Parameters]
B --> G[Same Return Type]
C --> H[Parent Implementation]
C --> I[Child Implementation]
D --> J[Dynamic Method Dispatch]
D --> K[Late Binding]
style A fill:#e3f2fd
style D fill:#f3e5f5
๐ฏ Method Overriding
- โข Child redefines parent method
- โข Same method signature required
- โข Runtime method selection
- โข Enables polymorphism
โก Dynamic Dispatch
- โข Method resolved at runtime
- โข Based on actual object type
- โข Enables flexible design
- โข Foundation of OOP
layout: default#
Overriding vs Overloading#
๐ Method Overriding#
// Parent class
class Animal {
public void makeSound() {
System.out.println("Animal makes a generic sound");
}
public void move() {
System.out.println("Animal moves");
}
}
// Child class - Method Overriding
class Dog extends Animal {
@Override
public void makeSound() { // Same signature
System.out.println("Dog barks: Woof! Woof!");
}
@Override
public void move() { // Same signature
System.out.println("Dog runs on four legs");
}
}
// Usage
Animal animal = new Dog(); // Polymorphism
animal.makeSound(); // Calls Dog's version (Runtime decision)
animal.move(); // Calls Dog's version (Runtime decision)
Key Features:
- Same method signature
- Runtime method resolution
- Inheritance relationship required
- Enables polymorphism
๐ง Method Overloading (Review)#
// Method Overloading in same class
class Calculator {
// Same method name, different parameters
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) { // Different types
return a + b;
}
public int add(int a, int b, int c) { // Different count
return a + b + c;
}
}
// Usage
Calculator calc = new Calculator();
calc.add(5, 10); // Calls int version (Compile-time decision)
calc.add(5.5, 10.7); // Calls double version (Compile-time decision)
calc.add(1, 2, 3); // Calls three-parameter version
Key Features:
- Different method signatures
- Compile-time method resolution
- Same class or inheritance
- Static polymorphism
layout: default#
Basic Method Overriding#
๐ฏ Simple Overriding Example#
// Base class
class Vehicle {
protected String brand;
protected double speed;
public Vehicle(String brand) {
this.brand = brand;
this.speed = 0.0;
}
// Method to be overridden
public void start() {
System.out.println(brand + " vehicle is starting");
speed = 10.0;
}
public void stop() {
System.out.println(brand + " vehicle is stopping");
speed = 0.0;
}
public void displaySpeed() {
System.out.println("Current speed: " + speed + " km/h");
}
// Method that shows vehicle type
public void showVehicleType() {
System.out.println("This is a generic vehicle");
}
}
// Derived class
class Car extends Vehicle {
private boolean engineRunning;
public Car(String brand) {
super(brand);
this.engineRunning = false;
}
// Override parent method
@Override
public void start() {
System.out.println("Turning key to start " + brand + " car");
System.out.println("Engine started!");
engineRunning = true;
speed = 15.0; // Car starts faster than generic vehicle
}
// Override parent method
@Override
public void stop() {
System.out.println("Applying brakes on " + brand + " car");
System.out.println("Engine stopped!");
engineRunning = false;
speed = 0.0;
}
// Override parent method
@Override
public void showVehicleType() {
System.out.println("This is a car - " + brand);
}
}
๐ Overriding in Action#
public class OverridingDemo {
public static void main(String[] args) {
// Direct object creation
System.out.println("=== Direct Object Creation ===");
Car car = new Car("Toyota");
car.start(); // Calls Car's overridden method
car.displaySpeed(); // Inherited from Vehicle
car.showVehicleType(); // Calls Car's overridden method
car.stop(); // Calls Car's overridden method
System.out.println("\n=== Polymorphic Behavior ===");
// Polymorphic reference - Vehicle reference to Car object
Vehicle vehicle = new Car("Honda");
// All these calls go to Car's overridden methods
vehicle.start(); // Car's start() method
vehicle.displaySpeed(); // Vehicle's method (inherited)
vehicle.showVehicleType(); // Car's overridden method
vehicle.stop(); // Car's overridden method
System.out.println("\n=== Method Resolution Verification ===");
System.out.println("Object type: " + vehicle.getClass().getSimpleName());
System.out.println("Reference type: Vehicle");
System.out.println("Methods called are from: " +
vehicle.getClass().getSimpleName());
}
}
Expected Output:
=== Direct Object Creation ===
Turning key to start Toyota car
Engine started!
Current speed: 15.0 km/h
This is a car - Toyota
Applying brakes on Toyota car
Engine stopped!
=== Polymorphic Behavior ===
Turning key to start Honda car
Engine started!
Current speed: 15.0 km/h
This is a car - Honda
Applying brakes on Honda car
Engine stopped!
layout: default#
@Override Annotation#
๐ก๏ธ Benefits of @Override#
Compile-time Safety:
- Ensures method actually overrides parent method
- Catches typos in method names
- Validates method signatures
Code Documentation:
- Clear intent to override
- Helps other developers understand
- IDE support and warnings
Refactoring Safety:
- Alerts if parent method signature changes
- Prevents accidental method hiding
- Maintains inheritance contracts
โ Without @Override#
class Animal {
public void makeSound() {
System.out.println("Animal sound");
}
}
class Cat extends Animal {
// Typo in method name - no compilation error!
public void makesound() { // Missing capital S
System.out.println("Cat meows");
}
}
// Usage
Animal cat = new Cat();
cat.makeSound(); // Prints "Animal sound" - not what we wanted!
โ With @Override#
class Animal {
public void makeSound() {
System.out.println("Animal sound");
}
public void sleep(int hours) {
System.out.println("Animal sleeps for " + hours + " hours");
}
}
class Cat extends Animal {
@Override
public void makeSound() { // โ
Correct override
System.out.println("Cat meows");
}
// This would cause compilation error
// @Override
// public void makesound() { // โ Method doesn't exist in parent
// System.out.println("Cat meows");
// }
@Override
public void sleep(int hours) { // โ
Correct override
System.out.println("Cat sleeps curled up for " + hours + " hours");
}
// This would cause compilation error
// @Override
// public void sleep(double hours) { // โ Different parameter type
// System.out.println("Cat sleeps");
// }
}
// Safe usage
Animal cat = new Cat();
cat.makeSound(); // โ
Guaranteed to call Cat's method
cat.sleep(8); // โ
Guaranteed to call Cat's method
layout: default#
Method Overriding Rules#
๐ Overriding Rules#
Method Signature:
- Method name must be identical
- Parameter list must be identical
- Return type must be same or covariant
Access Modifiers:
- Cannot reduce visibility
- Can increase visibility
- private methods cannot be overridden
Exceptions:
- Cannot throw new checked exceptions
- Can throw fewer or narrower exceptions
- Unchecked exceptions have no restrictions
Other Rules:
- static methods cannot be overridden (hidden instead)
- final methods cannot be overridden
- Constructor cannot be overridden
๐ Rules Demonstration#
class Parent {
// Protected method
protected void method1() throws IOException {
System.out.println("Parent method1");
}
// Public method with return type
public Number method2() {
return Integer.valueOf(10);
}
// Final method - cannot be overridden
public final void method3() {
System.out.println("Final method");
}
// Static method - can be hidden, not overridden
public static void method4() {
System.out.println("Parent static method");
}
// Private method - not inherited, cannot be overridden
private void method5() {
System.out.println("Private method");
}
}
class Child extends Parent {
// โ
Valid override - increased visibility
@Override
public void method1() { // protected โ public (allowed)
System.out.println("Child method1");
// No exception thrown (allowed - fewer exceptions)
}
// โ
Valid override - covariant return type
@Override
public Integer method2() { // Number โ Integer (covariant)
return Integer.valueOf(20);
}
// โ This would cause compilation error
// @Override
// public void method3() { // Cannot override final method
// System.out.println("Child method3");
// }
// โ
Method hiding (not overriding)
public static void method4() {
System.out.println("Child static method");
}
// โ
New method (not overriding private method)
public void method5() {
System.out.println("Child method5");
}
}
layout: default#
Dynamic Method Dispatch#
โก Runtime Method Resolution#
// Demonstration of dynamic method dispatch
class Shape {
protected String name;
public Shape(String name) {
this.name = name;
}
public void draw() {
System.out.println("Drawing a generic shape: " + name);
}
public double calculateArea() {
System.out.println("Area calculation not implemented for generic shape");
return 0.0;
}
public void displayInfo() {
System.out.println("Shape: " + name);
System.out.println("Area: " + calculateArea());
}
}
class Circle extends Shape {
private double radius;
public Circle(double radius) {
super("Circle");
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Drawing a circle with radius: " + radius);
}
@Override
public double calculateArea() {
double area = Math.PI * radius * radius;
System.out.println("Calculating circle area: ฯ ร " + radius + "ยฒ");
return area;
}
}
class Rectangle extends Shape {
private double width, height;
public Rectangle(double width, double height) {
super("Rectangle");
this.width = width;
this.height = height;
}
@Override
public void draw() {
System.out.println("Drawing a rectangle: " + width + " ร " + height);
}
@Override
public double calculateArea() {
double area = width * height;
System.out.println("Calculating rectangle area: " + width + " ร " + height);
return area;
}
}
๐ญ Polymorphic Behavior#
public class DynamicDispatchDemo {
public static void main(String[] args) {
// Array of Shape references pointing to different objects
Shape[] shapes = {
new Circle(5.0),
new Rectangle(4.0, 6.0),
new Circle(3.0),
new Rectangle(10.0, 2.0)
};
System.out.println("=== Dynamic Method Dispatch Demo ===");
// Same method call, different implementations executed
for (Shape shape : shapes) {
System.out.println("\n--- Processing " + shape.name + " ---");
// These method calls are resolved at runtime
shape.draw(); // Calls appropriate draw() method
shape.displayInfo(); // Uses overridden calculateArea()
// Verify actual object type
System.out.println("Actual object type: " +
shape.getClass().getSimpleName());
}
System.out.println("\n=== Method Resolution Details ===");
demonstrateMethodResolution();
}
private static void demonstrateMethodResolution() {
Shape shape1 = new Circle(7.0);
Shape shape2 = new Rectangle(5.0, 8.0);
// Same reference type, different object types
System.out.println("shape1 reference type: Shape");
System.out.println("shape1 object type: " + shape1.getClass().getSimpleName());
shape1.calculateArea(); // Calls Circle's method
System.out.println("\nshape2 reference type: Shape");
System.out.println("shape2 object type: " + shape2.getClass().getSimpleName());
shape2.calculateArea(); // Calls Rectangle's method
// Method binding happens at runtime based on actual object type
System.out.println("\nMethod binding: RUNTIME (Dynamic)");
}
}
Key Points:
- Method resolution happens at runtime
- Based on actual object type, not reference type
- Enables true polymorphic behavior
- Foundation of flexible OOP design
layout: default#
Real-World Example: Employee Management System#
๐ฅ Employee Hierarchy with Overriding#
abstract class Employee {
protected String employeeId;
protected String name;
protected String department;
protected double baseSalary;
public Employee(String employeeId, String name, String department, double baseSalary) {
this.employeeId = employeeId;
this.name = name;
this.department = department;
this.baseSalary = baseSalary;
}
// Methods to be overridden by subclasses
public abstract double calculateSalary();
public abstract void displayRole();
// Common method that can be overridden
public void clockIn() {
System.out.println(name + " (" + employeeId + ") clocked in at " +
java.time.LocalTime.now());
}
public void displayBasicInfo() {
System.out.println("ID: " + employeeId);
System.out.println("Name: " + name);
System.out.println("Department: " + department);
System.out.println("Base Salary: $" + baseSalary);
}
// Template method using overridden methods
public void generatePaySlip() {
System.out.println("=== PAY SLIP ===");
displayBasicInfo();
displayRole();
System.out.println("Total Salary: $" + String.format("%.2f", calculateSalary()));
System.out.println("================");
}
}
class Manager extends Employee {
private double bonusPercentage;
private int teamSize;
public Manager(String employeeId, String name, String department,
double baseSalary, double bonusPercentage, int teamSize) {
super(employeeId, name, department, baseSalary);
this.bonusPercentage = bonusPercentage;
this.teamSize = teamSize;
}
@Override
public double calculateSalary() {
double bonus = baseSalary * bonusPercentage;
double teamBonus = teamSize * 100; // $100 per team member
return baseSalary + bonus + teamBonus;
}
@Override
public void displayRole() {
System.out.println("Role: Manager");
System.out.println("Team Size: " + teamSize);
System.out.println("Bonus Percentage: " + (bonusPercentage * 100) + "%");
}
@Override
public void clockIn() {
System.out.println("Manager " + name + " accessed management portal");
super.clockIn(); // Call parent implementation
}
}
๐ป Developer and Sales Classes#
class Developer extends Employee {
private String programmingLanguage;
private int projectsCompleted;
public Developer(String employeeId, String name, double baseSalary,
String language, int projectsCompleted) {
super(employeeId, name, "IT", baseSalary);
this.programmingLanguage = language;
this.projectsCompleted = projectsCompleted;
}
@Override
public double calculateSalary() {
double projectBonus = projectsCompleted * 500; // $500 per project
return baseSalary + projectBonus;
}
@Override
public void displayRole() {
System.out.println("Role: Software Developer");
System.out.println("Programming Language: " + programmingLanguage);
System.out.println("Projects Completed: " + projectsCompleted);
}
@Override
public void clockIn() {
System.out.println("Developer " + name + " started coding session");
super.clockIn();
}
}
class SalesRepresentative extends Employee {
private double commissionRate;
private double salesAmount;
public SalesRepresentative(String employeeId, String name, double baseSalary,
double commissionRate, double salesAmount) {
super(employeeId, name, "Sales", baseSalary);
this.commissionRate = commissionRate;
this.salesAmount = salesAmount;
}
@Override
public double calculateSalary() {
double commission = salesAmount * commissionRate;
return baseSalary + commission;
}
@Override
public void displayRole() {
System.out.println("Role: Sales Representative");
System.out.println("Commission Rate: " + (commissionRate * 100) + "%");
System.out.println("Sales Amount: $" + salesAmount);
}
@Override
public void clockIn() {
System.out.println("Sales Rep " + name + " checked sales dashboard");
super.clockIn();
}
}
// Payroll system using polymorphism
class PayrollSystem {
public static void processPayroll(Employee[] employees) {
System.out.println("=== MONTHLY PAYROLL PROCESSING ===\n");
double totalPayroll = 0.0;
for (Employee emp : employees) {
emp.clockIn(); // Different implementation for each type
emp.generatePaySlip(); // Uses overridden calculateSalary()
totalPayroll += emp.calculateSalary();
System.out.println();
}
System.out.println("Total Payroll: $" + String.format("%.2f", totalPayroll));
}
}
layout: default#
Covariant Return Types#
๐ Return Type Flexibility#
// Base class
class Animal {
public Animal reproduce() {
System.out.println("Animal reproduces");
return new Animal();
}
public void showType() {
System.out.println("This is an Animal");
}
}
// Child class with covariant return type
class Dog extends Animal {
@Override
public Dog reproduce() { // โ
Covariant return type
System.out.println("Dog reproduces");
return new Dog(); // Returns Dog instead of Animal
}
@Override
public void showType() {
System.out.println("This is a Dog");
}
public void bark() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
@Override
public Cat reproduce() { // โ
Covariant return type
System.out.println("Cat reproduces");
return new Cat();
}
@Override
public void showType() {
System.out.println("This is a Cat");
}
public void meow() {
System.out.println("Cat meows");
}
}
๐ฏ Covariant Return Benefits#
public class CovariantReturnDemo {
public static void main(String[] args) {
System.out.println("=== Covariant Return Type Demo ===");
// Using polymorphic references
Animal animal1 = new Dog();
Animal animal2 = new Cat();
// Method calls return Animal reference (compile-time type)
Animal offspring1 = animal1.reproduce(); // Actually returns Dog
Animal offspring2 = animal2.reproduce(); // Actually returns Cat
offspring1.showType(); // Shows "This is a Dog"
offspring2.showType(); // Shows "This is a Cat"
System.out.println("\n=== Direct Object References ===");
// Using direct references
Dog dog = new Dog();
Cat cat = new Cat();
// Method calls return specific types (no casting needed)
Dog puppy = dog.reproduce(); // Returns Dog directly
Cat kitten = cat.reproduce(); // Returns Cat directly
puppy.bark(); // Can call Dog-specific methods
kitten.meow(); // Can call Cat-specific methods
System.out.println("\n=== Type Verification ===");
System.out.println("offspring1 actual type: " + offspring1.getClass().getSimpleName());
System.out.println("offspring2 actual type: " + offspring2.getClass().getSimpleName());
System.out.println("puppy type: " + puppy.getClass().getSimpleName());
System.out.println("kitten type: " + kitten.getClass().getSimpleName());
}
}
Benefits of Covariant Return Types:
- No need for explicit casting
- Type safety at compile time
- More specific return types
- Better API design
- Natural inheritance hierarchy
layout: default#
Method Hiding vs Method Overriding#
๐ Instance Method Overriding#
class Parent {
// Instance method - can be overridden
public void instanceMethod() {
System.out.println("Parent instance method");
}
// Static method - will be hidden, not overridden
public static void staticMethod() {
System.out.println("Parent static method");
}
}
class Child extends Parent {
// Method overriding - runtime dispatch
@Override
public void instanceMethod() {
System.out.println("Child instance method");
}
// Method hiding - compile-time dispatch
public static void staticMethod() {
System.out.println("Child static method");
}
}
public class HidingVsOverridingDemo {
public static void main(String[] args) {
// Instance method overriding
System.out.println("=== Instance Method Overriding ===");
Parent parent1 = new Parent();
Parent parent2 = new Child(); // Polymorphic reference
Child child = new Child();
parent1.instanceMethod(); // "Parent instance method"
parent2.instanceMethod(); // "Child instance method" (OVERRIDDEN)
child.instanceMethod(); // "Child instance method"
System.out.println("\n=== Static Method Hiding ===");
// Static method hiding
Parent.staticMethod(); // "Parent static method"
Child.staticMethod(); // "Child static method"
// Static methods are resolved at compile time
parent1.staticMethod(); // "Parent static method"
parent2.staticMethod(); // "Parent static method" (HIDDEN, not overridden)
child.staticMethod(); // "Child static method"
}
}
๐ Comparison Table#
| Aspect | Method Overriding | Method Hiding |
|---|---|---|
| Method Type | Instance methods | Static methods |
| Binding Time | Runtime (Late) | Compile-time (Early) |
| Keyword | @Override | No annotation |
| Resolution | Based on object type | Based on reference type |
| Polymorphism | โ Enabled | โ Not enabled |
๐ฏ Practical Implications#
class Utility {
public static void process() {
System.out.println("Generic processing");
}
public void execute() {
System.out.println("Generic execution");
}
}
class SpecializedUtility extends Utility {
// Method hiding
public static void process() {
System.out.println("Specialized processing");
}
// Method overriding
@Override
public void execute() {
System.out.println("Specialized execution");
}
}
// Usage patterns
public void demonstrate() {
Utility util1 = new SpecializedUtility();
// Static method - resolved by reference type
util1.process(); // "Generic processing" (HIDING)
// Instance method - resolved by object type
util1.execute(); // "Specialized execution" (OVERRIDING)
}
layout: default#
Practical Exercise: Media Player System#
๐ต Design Challenge#
Requirements:
- Create an abstract MediaPlayer base class
- Implement different media players with overridden methods
- Use @Override annotation properly
- Demonstrate dynamic method dispatch
- Include covariant return types where appropriate
- Show polymorphic behavior in action
public abstract class MediaPlayer {
// TODO: Common properties (name, currentTrack, volume, etc.)
// TODO: Abstract methods (play, pause, stop, loadMedia)
// TODO: Concrete methods that use abstract methods
// TODO: Template method pattern implementation
}
public class AudioPlayer extends MediaPlayer {
// TODO: Audio-specific properties and methods
// TODO: Override abstract methods with audio implementation
// TODO: Add audio-specific functionality
}
public class VideoPlayer extends MediaPlayer {
// TODO: Video-specific properties and methods
// TODO: Override abstract methods with video implementation
// TODO: Add video-specific functionality
}
// TODO: Create demonstration class showing polymorphism
๐ฏ Expected Implementation#
Features to Implement:
- Abstract MediaPlayer base class with template methods
- AudioPlayer and VideoPlayer concrete implementations
- Method overriding with @Override annotation
- Dynamic method dispatch demonstration
- Covariant return types where applicable
- Polymorphic collection of different players
Success Criteria:
- All abstract methods properly overridden
- @Override annotation used correctly
- Dynamic method dispatch working
- Polymorphic behavior demonstrated
- Template method pattern implemented
- Real-world applicability shown
Usage Example:
// Should work after implementation
MediaPlayer[] players = {
new AudioPlayer("Spotify"),
new VideoPlayer("YouTube"),
new AudioPlayer("iTunes")
};
for (MediaPlayer player : players) {
player.loadMedia("sample_file"); // Polymorphic call
player.play(); // Dynamic dispatch
player.displayStatus(); // Template method
}
// Demonstrate covariant return types
AudioPlayer audioPlayer = new AudioPlayer("Pandora");
AudioTrack track = audioPlayer.getCurrentTrack(); // Covariant return
layout: default#
Common Method Overriding Mistakes#
โ Method Overriding Pitfalls
// WRONG: Reducing access visibility class Parent { public void method() { } } class Child extends Parent { @Override protected void method() { } // โ Cannot reduce visibility }
// WRONG: Adding checked exceptions class Parent { public void method() { } } class Child extends Parent { @Override public void method() throws IOException { } // โ New checked exception }
</div>
<div>
```java
// WRONG: Trying to override static methods
class Parent {
public static void method() { }
}
class Child extends Parent {
@Override // โ Compilation error
public static void method() { } // Cannot override static
}
// WRONG: Trying to override final methods
class Parent {
public final void method() { }
}
class Child extends Parent {
@Override // โ Compilation error
public void method() { } // Cannot override final
}
// WRONG: Not using @Override annotation
class Child extends Parent {
public void method() { } // โ Missing @Override, prone to errors
}
โ Correct Approaches
// CORRECT: Maintaining or increasing visibility class Parent { protected void method() { } } class Child extends Parent { @Override public void method() { } // โ Increased visibility }
// CORRECT: Not adding new checked exceptions class Parent { public void method() throws IOException { } } class Child extends Parent { @Override public void method() { } // โ Fewer exceptions allowed }
</div>
<div>
```java
// CORRECT: Method hiding for static methods
class Parent {
public static void method() { }
}
class Child extends Parent {
// Method hiding (no @Override)
public static void method() { } // โ
Hiding, not overriding
}
// CORRECT: Cannot override final - create new method
class Parent {
public final void method() { }
}
class Child extends Parent {
public void alternativeMethod() { } // โ
New method instead
}
// CORRECT: Always use @Override
class Child extends Parent {
@Override
public void method() { } // โ
Safe overriding
}
layout: center class: text-center#
Summary#
๐ What We Learned
- โข Method overriding vs method overloading
- โข @Override annotation benefits and usage
- โข Dynamic method dispatch and runtime binding
- โข Method overriding rules and restrictions
- โข Covariant return types
- โข Method hiding vs method overriding
๐ฏ Next Steps
- โข super keyword and constructor chaining
- โข Abstract classes and methods
- โข Interface implementation and overriding
- โข Polymorphism and design patterns
- โข Advanced OOP concepts

