Вызов контравариантных методов в 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");
Таким образом, правило состоит в том, что для переопределения метода необходимо иметь один и тот же список аргументов. (Есть некоторые ситуации, когда вы можете переопределить, когда списки аргументов не идентичны, но имеют одинаковое удаление). Точные определения приведены здесь.