Вызов контравариантных методов в 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?

2 ответа

Решение

Java наследует все методы от своего родителя, если они не были переопределены или скрыты. Это означает, что ваш B класс такой же как

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 при условии обратной совместимости.

Сначала может показаться, что вы должны иметь возможность переопределить метод и принять более общие аргументы (аналогично тому, что вы можете переопределить метод и вернуть более конкретный тип).

Однако это внесло бы большую сложность в язык. Например,

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");

Таким образом, правило состоит в том, что для переопределения метода необходимо иметь один и тот же список аргументов. (Есть некоторые ситуации, когда вы можете переопределить, когда списки аргументов не идентичны, но имеют одинаковое удаление). Точные определения приведены здесь.

Другие вопросы по тегам