通过将期望超类型的子类对象作为参数传递来覆盖方法


12

我只是在学习Java,而不是一名实践的程序员。

我要遵循的书说,重写方法时,参数类型必须相同,但是返回类型可以是多态兼容的。

我的问题是为什么传递给重写方法的参数不能不是预期的超类型的子类类型?

在重载方法中,可以保证在对象上定义了我对对象调用的任何方法。


有关建议的重复项的注意事项:

一个建议似乎是关于类层次结构以及在何处放置功能。我的问题更集中于为什么存在语言限制。

第二个建议,说明如何做我问什么,而不是为什么它做的方式。我的问题集中在为什么。


1
在先前的问题中被问及回答:“通常认为这没有通过Liskov替换原理(LSP),因为增加的约束使得基类操作并不总是适用于派生类...”
gnat


@gnat问题不是要求参数使用更具体的子类型是否违反某些计算机原理,问题是edalorzo回答了。
user949300 2014年

Answers:


18

您最初在问题中提到的概念称为协变返回类型

协变返回类型之所以起作用,是因为方法应该返回某种类型的对象,而重写方法实际上可能返回该对象的子类。根据Java之类的语言的子类型化规则,如果S是的子类型T,那么无论T出现在哪里,我们都可以传递S

因此,S在覆盖预期a的方法时,返回a 是安全的T

您建议接受覆盖方法使用作为覆盖方法所请求参数的子类型的参数的建议要复杂得多,因为这会导致类型系统不健全。

一方面,通过上述相同的子类型化规则,很可能它已经可以满足您的需求。例如

interface Hunter {
   public void hunt(Animal animal);
}

没有什么可以阻止此类的实现接收任何种类的动物的,因为它已经满足了您提出的问题的标准。

但让我们假设我们可以按照您的建议覆盖此方法:

class MammutHunter implements Hunter {
  @Override
  public void hunt(Mammut animal) {
  }
}

这是有趣的部分,现在您可以执行以下操作:

AnimalHunter hunter = new MammutHunter();
hunter.hunt(new Bear()); //Uh oh

根据AnimalHunter您的公共接口,您应该可以猎杀任何动物,但是根据您的实现,MammutHunter您只能接受Mammut对象。因此,重写的方法不满足公共接口。我们只是在这里破坏了类型系统的健全性。

您可以使用泛型来实现所需的功能。

interface AnimalHunter<T extends Animal> {
   void hunt(T animal);
}

然后,您可以定义您的MammutHunter

class MammutHunter implements AnimalHunter<Mammut> {
   void hunt(Mammut m){
   }
}

使用通用协方差和逆方差,您可以在必要时放宽对自己有利的规则。例如,我们可以确保哺乳动物猎人只能在给定的背景下猎取猫科动物:

AnimalHunter<? super Feline> hunter = new MammalHunter();
hunter.hunt(new Lion());
hunter.hunt(new Puma());

假设MammalHunter工具AnimalHunter<Mammal>

在这种情况下,这将不被接受:

hunter.hunt(new Mammut()):

即使当哺乳动物是哺乳动物时,由于我们在此使用的逆变类型的限制,它也不被接受。因此,您仍然可以对类型进行某些控制,以执行您提到的操作。


3

问题不是您在给定对象作为参数的覆盖方法中执行的操作。问题是允许使用您的方法的代码向您的方法提供参数的类型是什么。

覆盖方法必须满足覆盖方法的约定。如果重写的方法接受某个类的参数,而您使用仅接受子类的方法重写它,则该合同不履行。这就是为什么它无效的原因。

用更具体的参数类型覆盖方法称为重载。它定义了一个具有相同名称但具有不同或更具体参数类型的方法。除了原始方法外,还可以使用重载方法。调用哪种方法取决于编译时已知的类型。要重载方法,您必须删除@Override批注。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.