Search code examples
javaoopcontravariance

Invocation of contravariant methods in Java


Given a situation like the below:

interface Base { }
interface Derived extends Base{ }
interface OtherDerived extends Base{ }

class A {
    void implementation(Derived stuff) { 
    // Implementation A 
    }
}

class B extends A {
    // contravariant; does not override
    void implementation(Base stuff) { 
    // Implementation B 
    }
}

Method calls in this code are dispatched like so:

(new B()).implementation(derivedObject);  // Ex. 1: calls A.implementation
(new B()).implementation(baseObject);  // Ex. 1: calls B.implementation
(new B()).implementation(otherDerivedObject());  // Ex. 2: calls B.implementation

What I've always wondered is, why does Java treat the contravariant method (B.implementation) essentially like an overload (other than that the signatures of A.implementation and B.implementation are not override equivalent). Is there an intentional reason for dispatching to the most specific method signature (and if so, would you point me to where in the Java spec this is explicitly spelled out?), or is it just an accidental consequence of how overriding is implemented in Java?


Solution

  • Java inherits all methods from it's parent unless they have been overridden or hidden. This means your B class is the same as

    class A {
        void implementation(Derived stuff) { 
        // Implementation A 
        }
    }
    
    class B extends A {
        // contravariant; does not override
        void implementation(Base stuff) { 
        // Implementation B 
        }
    
        void implementation(Derived stuff) { 
            super.implementation(stuff);
        }
    }
    

    The advantage of this is that say you have

    class A {
        void implementation(Derived stuff) { 
        // Implementation A 
        }
    }
    
    class B extends A {
    }
    

    and

    new B().implementation(new Derived());
    

    this compiled code doesn't change it's behaviour if you later add a method to B provided backward compatibility.