Method Overloading / Overriding — Practice Tasks¶
Twelve exercises in distinguishing the two mechanisms and reasoning about resolution.
Task 1 — Predict resolution¶
Given:
class A {
void m(Object o) { System.out.println("Object"); }
void m(String s) { System.out.println("String"); }
}
A a = new A();
String s = "hi";
Object o = s;
a.m(s); // ?
a.m(o); // ?
a.m((Object) s); // ?
Predict each. Then run.
Task 2 — Override correctly¶
class Animal {
public Number compute() throws IOException { return 0; }
}
class Dog extends Animal {
// Add a covariant override that returns Integer and throws no checked exceptions
}
Use @Override. Verify with javap -v that a bridge method was generated.
Task 3 — Detect missing override¶
class Parent {
void process(Object o) { }
}
class Child extends Parent {
void process(String s) { } // intended as override?
}
Add @Override to Child.process. What does the compiler say? Refactor to make it a true override.
Task 4 — Static method "override"¶
class Parent { static String name() { return "Parent"; } }
class Child extends Parent { static String name() { return "Child"; } }
Parent p = new Child();
p.name(); // ?
Predict. Why does it say "Parent"? What's the rule?
Task 5 — Overload with autoboxing¶
void m(int x) { System.out.println("int"); }
void m(Integer x) { System.out.println("Integer"); }
void m(long x) { System.out.println("long"); }
m(5); // ?
m(Integer.valueOf(5)); // ?
m(5L); // ?
m(null); // ?
Predict each. Run.
Task 6 — Final method override attempt¶
class Base { public final void compute() { } }
class Derived extends Base {
@Override public void compute() { } // ?
}
Compile. What's the error? Why?
Task 7 — Bridge method observation¶
Given:
class Box<T> { void put(T x) { } }
class IntBox extends Box<Integer> {
@Override void put(Integer x) { }
}
Compile and run javap -p -v IntBox.class. Find the bridge method. What's its signature?
Task 8 — Diamond default conflict¶
interface X { default String name() { return "X"; } }
interface Y { default String name() { return "Y"; } }
class Z implements X, Y { }
What's the compile error? Add code to disambiguate — return both names.
Task 9 — Overloading with generics erasure¶
Compile. What's the error? Why? How would you handle this if you really need to differentiate?
Task 10 — Megamorphic dispatch¶
Create an interface Op with method int apply(int a, int b). Implement it 5 ways: PLUS, MINUS, TIMES, DIVIDE, MOD. Loop through a List<Op> calling apply on each. Profile with -XX:+PrintInlining. Observe whether dispatch is monomorphic, bimorphic, or megamorphic.
Task 11 — Pattern matching to replace dispatch¶
Convert:
double area(Shape s) {
if (s instanceof Circle) return Math.PI * ((Circle) s).r() * ((Circle) s).r();
if (s instanceof Square) return ((Square) s).s() * ((Square) s).s();
return 0;
}
…to use sealed types + pattern matching switch. What does this gain over polymorphic dispatch via Shape.area()?
Task 12 — Overload that breaks LSP¶
Design two methods on a Shape class:
class Shape {
void resize(int factor) { ... }
}
class Circle extends Shape {
void resize(double factor) { ... } // intended as override?
}
Why doesn't this override? Demonstrate that calling resize(2) on a Circle reference dispatches differently than on a Shape reference.
Validation¶
| Task | How |
|---|---|
| 1 | "String", "Object", "Object" — explain why |
| 2 | Compiler accepts; bridge method Number compute() exists in Dog |
| 3 | @Override on process(String) produces error; rename to process(Object) to override correctly |
| 4 | Output is "Parent" — static dispatch via declared type |
| 5 | "int", "Integer", "long", "Integer" — explain phases |
| 6 | Compile error: "compute() is final in Base" |
| 7 | javap shows synthetic void put(Object) bridge |
| 8 | Compile error: "Z inherits unrelated defaults"; override returns "X / Y" |
| 9 | Compile error: same erasure; differentiate via wrapping or different method names |
| 10 | Loop is megamorphic; PrintInlining shows "callee not inlineable" |
| 11 | Pattern matching is exhaustive; adding a new shape errors at compile time |
| 12 | circle.resize(2) calls Circle.resize(double); ((Shape)circle).resize(2) calls Shape.resize(int) |
Memorize this: overloading is compile-time, based on parameter types. Overriding is runtime, based on receiver class. @Override catches subtle bugs. Bridge methods preserve binary compatibility. Erasure prevents some overloads. Sealed types and pattern matching offer modern alternatives to inheritance-based polymorphism.