Method Overriding and Overloading in Java
Question
Compare and contrast method overriding and method overloading in Java. Explain their purposes, implementation requirements, and use cases. How do they contribute to polymorphism and code reusability?
Answer
Method overriding and overloading are two forms of polymorphism in Java that serve different purposes and have distinct implementation requirements.
Method Overriding
-
Basic Overriding Example
public class Animal { public void makeSound() { System.out.println("Some sound"); } } public class Dog extends Animal { @Override public void makeSound() { System.out.println("Woof!"); } } public class Cat extends Animal { @Override public void makeSound() { System.out.println("Meow!"); } }
-
Runtime Polymorphism
public class Shape { public double calculateArea() { return 0.0; } } public class Circle extends Shape { private double radius; @Override public double calculateArea() { return Math.PI * radius * radius; } } public class Rectangle extends Shape { private double length; private double width; @Override public double calculateArea() { return length * width; } }
Method Overloading
-
Basic Overloading Example
public class Calculator { public int add(int a, int b) { return a + b; } public double add(double a, double b) { return a + b; } public String add(String a, String b) { return a + b; } }
-
Constructor Overloading
public class Person { private String name; private int age; public Person() { this.name = "Unknown"; this.age = 0; } public Person(String name) { this.name = name; this.age = 0; } public Person(String name, int age) { this.name = name; this.age = age; } }
Key Differences
-
Signature Requirements
public class OverrideExample { // Overriding - Same signature public class Parent { public void method(int x) {} } public class Child extends Parent { @Override public void method(int x) {} // Must match exactly } // Overloading - Different signatures public class OverloadExample { public void method(int x) {} public void method(double x) {} public void method(int x, int y) {} } }
-
Access Modifiers
public class AccessModifiers { public class Parent { protected void method() {} } public class Child extends Parent { @Override public void method() {} // Can be more accessible // @Override // private void method() {} // Cannot be less accessible } }
Best Practices
-
Override Annotation
public class AnnotationExample { public class Parent { public void method() {} } public class Child extends Parent { @Override // Good practice - catches errors public void method() {} // Without @Override - might miss typos public void metod() {} // Compiles but doesn't override } }
-
Covariant Return Types
public class CovariantReturn { public class Animal { public Animal create() { return new Animal(); } } public class Dog extends Animal { @Override public Dog create() { // Can return subclass return new Dog(); } } }
Common Pitfalls
-
Accidental Overloading
public class AccidentalOverload { public class Parent { public void method(Object obj) {} } public class Child extends Parent { public void method(String str) {} // Overloads, doesn't override @Override public void method(Object obj) {} // Correct override } }
-
Static Methods
public class StaticMethods { public class Parent { public static void method() { System.out.println("Parent"); } } public class Child extends Parent { public static void method() { // Hides, doesn't override System.out.println("Child"); } } }
Use Cases
-
Template Method Pattern
public class TemplateMethod { public abstract class Beverage { public final void prepare() { boilWater(); brew(); pourInCup(); addCondiments(); } protected abstract void brew(); protected abstract void addCondiments(); private void boilWater() { System.out.println("Boiling water"); } private void pourInCup() { System.out.println("Pouring in cup"); } } public class Coffee extends Beverage { @Override protected void brew() { System.out.println("Dripping coffee"); } @Override protected void addCondiments() { System.out.println("Adding sugar and milk"); } } }
-
Builder Pattern
public class BuilderPattern { public class Pizza { private String crust; private String sauce; private String topping; public static class Builder { private Pizza pizza = new Pizza(); public Builder crust(String crust) { pizza.crust = crust; return this; } public Builder sauce(String sauce) { pizza.sauce = sauce; return this; } public Builder topping(String topping) { pizza.topping = topping; return this; } public Pizza build() { return pizza; } } } }
Testing
-
Testing Overridden Methods
@Test public void testAnimalSounds() { Animal dog = new Dog(); Animal cat = new Cat(); // Runtime polymorphism assertEquals("Woof!", captureOutput(dog::makeSound)); assertEquals("Meow!", captureOutput(cat::makeSound)); }
-
Testing Overloaded Methods
@Test public void testCalculator() { Calculator calc = new Calculator(); assertEquals(5, calc.add(2, 3)); assertEquals(5.0, calc.add(2.0, 3.0)); assertEquals("23", calc.add("2", "3")); }