当前位置: 首页 > 知识库问答 >
问题:

为什么java允许用子类返回类型而不是子类参数重写方法?

林英锐
2023-03-14

如果我在java中有一个抽象的(或据我所知的)超类,如下所示:

public abstract class Person {
    public abstract Person getPerson(Person p);
}

我注意到,在重写这个方法时,子类可以使用自身作为返回类型,但不能使用参数:

public class Child extends Person {
    @Override
    public Child getPerson(Person p) { //Works fine
        return this;
    }

    @Override
    public Person getPerson(Child p) { //Doesn't work
        return this;
    }
}

为什么不允许这样做?我不是在寻求解决方案——我知道我可以在方法中使用< code>instanceof检查或其他方法,我更想知道为什么前者是可以接受的,而后者对编译器来说却不是。

共有3个答案

冯澄邈
2023-03-14

因为Java中的方法签名是方法的名称、参数的类型、编号和顺序。它不依赖于返回类型。

对于您的方案,getPerson(Person p) 方法存在于父类中,因此,通过继承规则可以重写它。但是方法获取人(子 p)不在父类中。这就是为什么它不起作用并抛出错误的原因。方法的返回类型在这里不起任何作用。希望这能解释你的好奇心。

宋华灿
2023-03-14

当参数允许这样做时,它们被称为逆变参数。当您使用返回类型时,它们被称为协变返回类型。Java支持协变返回类型,但不支持逆变参数。

这将改变方法签名,它将不再是一个覆盖。返回类型不是方法签名的一部分,但是方法的类型和参数数量是签名的一部分,因此这会干扰重载。

此外,如果Java允许您这样做,在某些情况下会导致意外的行为,并破坏虚拟方法的运行时多态性,因为您缩小了方法可以接受的范围。

假设某些代码或 API 仅向您公开基类。你调用someMethod()来接收一个人:

public Person someMethod()
{
    Child child = new Child();
    return child;
}

这是它在调用站点上的样子:

Person receivedPerson = someMethod();
Person myPerson = new Person();
Person result = receivedPerson.getPerson(myPerson); // This will fail

在这里,调用者不知道有方法()实际上返回了一个而不是一个Person。我认为Person类有一个名为getPerson()的方法,接受Person类型的对象,因此,我可以调用receivedPerson.getPerson(myPerson)。但是,由于您甚至不知道的派生类型儿童已将getPerson()的参数类型更改为儿童,它将无法接受myPerson,因为您无法将基类的对象转换为派生类的对象。

协变返回类型永远不会发生这种情况,因为如果一个方法返回一个更具体的类型,比如说< code>Child而不是< code>Person,它可以很容易地存储在< code>Person变量中,并且Child将始终具有其父级的所有状态和行为。

潘飞英
2023-03-14

这是因为编译器强制保证引用超类方法的客户机代码将与子类兼容。

因为你可以这样做:

Person person = new Child();
Person result = person.getPerson();

结果实际包含 Child 对象时,不会中断任何内容。孩子就是一个人

现在,如果您尝试将相同的逻辑应用于方法参数,也就是说,如果您假设可以使用< code>Child实例调用< code>getPerson(),您将会遇到一个问题:

Child child = new Child();

如果编译器允许这样做(作为对Child.getPerson(Child)的调用):

Person childResult = child.getPerson(child);

以下内容将被破坏:

Person childPerson = child;
Person childResult = childPerson.getPerson(childPerson); //broken

这被打破了,因为你认为你在调用“重写”,而你不是。编译器正在确保用<code>Person声明的合同。所有扩展person的类都支持getPerson()。可以添加<code>Child。getPerson(Child),但这不会覆盖Person。getPerson(Person),它将重载它。这是因为<code>个人。getPerson(Person)应该可以用Person的任何子类调用。如果你有一个不同的子类,<code>婴儿</code>,直接扩展<code>个人</code>,会怎么样?如果您调用<code>child,它将如何工作。获取人员(婴儿)?编译器强制要求任何类型的Person都可以传递给Person。getPerson(Person)或覆盖它的任何子类方法。

相比之下,这对于返回类型来说不是问题。只要重写方法返回一个< code>Person对象,调用方就能够处理它,因为调用方准备接收一个< code>Person对象,而不是任何特定的子类。

 类似资料:
  • 当重写超类的方法时,Java 允许返回类型是协变的。 为什么在覆盖方法时不允许相反的逆变参数类型?

  • 在java中,我们可以缩小返回类型和throws异常类型(甚至删除throws子句): 但是,参数类型呢(如果A采用< code>T,那么为什么B不采用< code >?超级T)如: 让我们考虑一下我认为完全符合逻辑的这段代码: 所以我要说的是,在使用的代码上下文中仍然有效。

  • 如果我有以下代码 首先,这甚至是可能的,因为它编译得很好。但是我不能使用中的方法执行操作: makeCan是否不同时返回Can和PromotionalCan?如果是这样,当返回PromotionalCan时,如何对变量进行操作?谢谢 编辑:PromotionalCan继承自Can,第二种方法是在PromotionalCan中

  • 问题内容: 我想理解为什么做出这个决定。通用异常有什么问题? (据我所知,泛型只是编译时的语法糖,Object无论如何它们都将转换为.class文件,因此有效地声明泛型类就好像其中的所有内容都是Object。。如果我错了,请更正我) 问题答案: 正如标记所说,类型是不可更改的,在以下情况下会出现问题: 两者和都被擦除为相同类型,JVM无法区分异常实例,因此也无法确定应执行哪个块。

  • 问题内容: 这是我的代码,但是我没有得到方法如何接受类型作为返回值。如何运作?谁能解释一下这种方法的工作原理? 提前致谢 :) 问题答案: 好的,所以首先要做的是: 这是一个不断扩大的原始类型转换,因此这是合法的。您可以: 但是您不能: 第二:它返回的根本不是ASCII码。Java执行Unicode。 碰巧的是,当创建Java时,Unicode仅定义了适合16位的代码点。因此,它被创建为2字节的无

  • 问题内容: 作为实验,我尝试扩展-array,如下所示: 在类本身中添加一些与排序,交换,子数组构建等有关的方法。但是我在编译时遇到了这个错误: 我很好奇:为什么Java不允许扩展数组? 问题答案: 扩展基本类型(例如a 或数组)会打开安全漏洞。如果Java允许您扩展数组,则采用数组的方法将变得不安全。这就是字符串为,而数组根本不能扩展的原因。 例如,您可以重写该方法,并返回不正确大小的数组。这有