如果我在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检查或其他方法,我更想知道为什么前者是可以接受的,而后者对编译器来说却不是。
因为Java中的方法签名是方法的名称、参数的类型、编号和顺序。它不依赖于返回类型。
对于您的方案,getPerson(Person p)
方法存在于父类中,因此,通过继承规则可以重写它。但是方法获取人(子 p)
不在父类中。这就是为什么它不起作用并抛出错误的原因。方法的返回类型在这里不起任何作用。希望这能解释你的好奇心。
当参数允许这样做时,它们被称为逆变参数。当您使用返回类型时,它们被称为协变返回类型。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将始终具有其父级的所有状态和行为。
这是因为编译器强制保证引用超类方法的客户机代码将与子类兼容。
因为你可以这样做:
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允许您扩展数组,则采用数组的方法将变得不安全。这就是字符串为,而数组根本不能扩展的原因。 例如,您可以重写该方法,并返回不正确大小的数组。这有