在 Java 中调用逆变方法

Invocation of contravariant methods in Java

假设情况如下:

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 
    }
}

此代码中的方法调用是这样调度的:

(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

我一直想知道的是,为什么 Java 将逆变方法 (B.implementation) 本质上视为重载(除了 A.implementation 和 B.implementation 不是覆盖等效项)。分派到最具体的方法签名是否有故意的原因(如果是这样,你能指出 Java 规范中明确说明的地方吗?),或者它只是如何覆盖的意外结果在 Java?

中实施

Java 从其父级继承所有方法,除非它们已被覆盖或隐藏。这意味着您的 B class 与

相同
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);
    }
}

这样做的好处是说你有

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

class B extends A {
}

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

如果您稍后向 B 添加方法以提供向后兼容性,此编译代码不会改变其行为。

起初看起来您应该能够覆盖一个方法并接受更通用的参数(类似于您可以覆盖一个方法和 return 更 具体类型).

但是,这会给语言带来很大的复杂性。例如,

class A {
    void foo(String s) {}
}

class B extends A {

    void foo(CharSequence s) { System.out.println(true); }

    void foo(Serializable s) { System.out.println(false); }
}

根据你的提议,这应该打印什么?

A a = new B();
a.foo("bar");

所以规则很简单,为了覆盖一个方法,你必须有相同的参数列表。 (在某些情况下,当参数列表不相同但具有相同的擦除时,您可以覆盖)。给出了精确的定义here