Java:在子类中重写抽象方法

8

我真的应该知道这个,但出于某种原因,我不理解以下内容。

我的抽象类包含以下抽象方法:

protected abstract RuleDTO createRowToBeCloned(RuleDTO ruleDTO);

我还有另一个类如下:

EvaluationRuleDTO extends from RuleDTO

然后在我的抽象类的子类中,我有以下实现,由于“必须覆盖或实现超类型方法”,因此不允许:

protected EvaluationRuleDTO createRowToBeCloned(EvaluationRuleDTO ruleDTO) {

然而,以下操作是允许的:
protected EvaluationRuleDTO createRowToBeCloned(RuleDTO ruleDTO) {

我知道这可能是一个基础问题,但我有点困惑。为什么我可以在重写的方法中返回RuleDTO子类,但我不能传入子类?

谢谢

6个回答

12

你正在违反 Liskov 原则:超类能够做的任何事情,子类都必须能够做到。超类声明了一个接受任何类型 RuleDTO 的方法。但是在你的子类中,你只接受 EvaluationRuleDTO 的实例。如果你这样做会发生什么?

RuleDTO rule = new EvaluationRuleDTO();
rule.createRowToBeCloned(new RuleDTO());

EvaluationRuleDTO是RuleDTO的一个实现,因此它必须满足RuleDTO所定义的合约。

虽然子类中的方法可能返回EvaluationRuleDTO实例而不是RuleDTO实例,但是由于该合约要求返回RuleDTO,因此可以这样做,因为EvaluationRuleDTO也是RuleDTO。


那正是我正在寻找的。谢谢! - Tobias Reich
“子类中的方法可以返回EvaluationRuleDTO实例而不是RuleDTO实例” => 我在这里没有看到任何问题,而且在Java中实际上是允许的。(也许在2012年之前不行?)这将编译得非常好:http://pastebin.com/Vr4xQPqk。你几乎总是有非常好的答案,但我完全不理解这个答案。 - Koray Tugay
@KorayTugay 我也没有看到任何问题。这句话的意思是:解释子类中的方法可以返回更具体的内容。 - JB Nizet

4
因为当你重写一个方法时,必须使用与参数类型相同或更普通(更宽)的类型作为参数类型,而不能使用更狭窄的类型。
想一想,如果你可以使用更狭窄的类型来重写方法,那么你将破坏多态性能力,你不同意吗?所以,这样做,你会违反Liskov Substitution Principle,正如JB Nizet在他的答案中所说。

1
引用您的回答中的话:“您必须使用相同类型或更通用(更宽)的类型作为参数类型,而不能使用更窄的类型”,但这并不适用于Java。尽管从Liskov原则的角度来看是正确的,但在Java中,方法参数类型必须是不变的。 - karakays

4
Java允许重写时使用返回类型协变,因此您可以将覆盖的返回类型指定为更派生的类型。但是,Java不允许参数类型协变进行重写。
前者安全的原因是返回的对象将至少具有较少派生类型的功能,因此依赖于该事实的客户端仍将能够正确地利用返回的对象。
但是对于参数来说情况并非如此。如果合法,用户可以调用抽象方法并传递一个较少派生的类型(因为这是抽象类上声明的类型),但是您的派生覆盖可能会尝试将参数作为更派生的类型(它不是),从而导致错误。
理论上,Java可以允许参数类型contra-variance,因为它是类型安全的:如果被覆盖的方法只期望less-derived参数,则无法意外地使用不存在的方法或字段。不幸的是,目前还不可用。

0

Java 1.5有协变返回类型,这就是为什么它是有效的。

 

子类方法的返回类型R2可能与超类方法的返回类型R1不同,但R2应该是R1的子类型。也就是说,子类可以返回超类返回类型的子类型。

0

在早期的Java中,情况并非如此,但在Java 5.0中进行了更改。

您不能在同一类中具有仅通过返回类型不同的签名的两个方法。直到J2SE 5.0版本发布之前,一个类也无法覆盖从超类继承的方法的返回类型。在本提示中,您将了解J2SE 5.0中的新功能,该功能允许协变返回类型。这意味着子类中的方法可以返回其类型为超类中具有相同签名的方法返回类型的子类的对象。此功能消除了过多的类型检查和转换的需要。

来源:http://www.java-tips.org/java-se-tips/java.lang/covariant-return-types.html

这意味着重写方法的返回类型将是被重写方法的返回类型的子类型。


0
请看下面的代码:
class A {
    A foo(A a) {
        return new A();
    }
}

class B extends A {
    @Override
    // Returning a subtype in the overriding method is fine,
    // but using a subtype in the argument list is NOT fine!
    B foo(B b) {
        b.bar();
        return new B();
    }
    void bar() {
        // B specific method!
    }
}

是的,没问题,B是A的一种,但如果有人这样做会发生什么:

B b = new B();
b.foo(new A());

A类没有bar方法。这就是为什么参数不能是被覆盖方法中参数类型的子类型。

在覆盖方法中返回A或B都可以。以下代码片段将编译并正常运行。

class A {
    A foo(A a) {
        return new B(); // B IS AN A so I can return B!
    }
}

class B extends A {
    @Override
    B foo(A b) {
        return new B(); // Overridden method returns A and 
                        // B IS AN A so I can return B!
    }

    public static void main(String[] args) {
        A b = new B();
        final A foo = b.foo(new B());
        // I can even cast foo to B!
        B cast = (B) foo;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接