Skip to content

Dynamic Binding Summary

Method Invocation Process

  • For Java to determine the most appropriate method to invoke
  • Two-step process:
    1. Compile-time: Determine method descriptor
    2. Runtime: Find and execute actual method

Compile-Time Step

  • Uses compile-time type of target
  • Searches for all accessible methods in target's class and supertypes
  • Checks for compatible methods with given arguments
  • Selects the most specific compatible method
  • Stores the method's descriptor in the generated bytecode
  • Example:
    Shape s = new Circle();  // compile-time type: Shape, runtime type: Circle
    s.getArea();            // Compiler looks for getArea() in Shape hierarchy
    

Method Specificity

  • Specificity between Overloaded methods
  • Method M is more specific than N if:
    • All arguments that can be passed to M can also be passed to N
    • The reverse is not necessarily true
  • Example:
    class Example {
        void draw(Circle c) { }      // More specific
        void draw(Shape s) { }       // Less specific
    
        public static void main(String[] args) {
            Example e = new Example();
            Circle c = new Circle();
            e.draw(c);  // Calls draw(Circle) - more specific method
        }
    }
    
  • If multiple methods are equally specific, the compiler generates a compilation error
  • Example:
    class Ambiguous {
        void method(String s, Object o) { }
        void method(Object o, String s) { }
    
        // method(new String(), new String()); // Compilation error - ambiguous
    }
    

Runtime Step

  • Uses runtime type of target
  • Searches for method starting from runtime type
  • Moves up class hierarchy if not found
  • Executes the first method that exactly matches the method descriptor stored at compile time
  • Example:
    class Animal {
        void makeSound() { System.out.println("Animal sound"); }
    }
    
    class Dog extends Animal {
        @Override
        void makeSound() { System.out.println("Woof!"); }
    }
    
    class Puppy extends Dog {
        // No makeSound() override
    }
    
    public class Test {
        public static void main(String[] args) {
            Animal a = new Puppy();  // Compile-time: Animal, Runtime: Puppy
            a.makeSound();           // Output: "Woof!" (from Dog class)
        }
    }
    

Method Search Order

  • The first matching method in Overridden methods

    1. Check runtime type class
    2. If not found, check parent class
    3. Continue up to Object class
    4. Use first matching method found
    
  • Example:

    class A {
        void foo() { System.out.println("A.foo()"); }
    }
    
    class B extends A {
        @Override
        void foo() { System.out.println("B.foo()"); }
    }
    
    class C extends B {
        // No foo() override - will use B's implementation
    }
    
    class D extends C {
        @Override
        void foo() { System.out.println("D.foo()"); }
    }
    
    // Usage
    A obj1 = new C();  
    obj1.foo(); // Calls B.foo() - C doesn't override, so uses parent B
    A obj2 = new D();  
    obj2.foo(); // Calls D.foo() - D has its own implementation
    

Class Methods (Static)

  • No dynamic binding
  • Resolved at compile time
  • Based on compile-time type only
  • Example:
    Shape s = new Circle();
    s.staticMethod();  // calls Shape's static method regardless of runtime type
    

Best Practices

  • Understand compile-time vs runtime types
  • Be aware of method resolution rules
  • Document overridden method behavior
  • Use @Override to clarify intentions
  • Consider making methods final if overriding not intended