8

我真的应该知道这一点,但由于某种原因,我不明白以下内容。

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

protected abstract RuleDTO createRowToBeCloned(RuleDTO ruleDTO);

我还有另一堂课如下:

EvaluationRuleDTO extends from RuleDTO

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

protected EvaluationRuleDTO createRowToBeCloned(EvaluationRuleDTO ruleDTO) {

但是,允许以下情况:

protected EvaluationRuleDTO createRowToBeCloned(RuleDTO ruleDTO) {

我意识到这可能是一个基本问题,但我有点困惑。为什么我可以在覆盖的方法中返回 RuleDTO 的子类,但我不能传入子类?

谢谢

4

6 回答 6

12

你打破了 Liskov 原则:超类可以做的一切,子类必须能够做。超类声明了一个接受任何类型 RuleDTO 的方法。但是在您的子类中,您只接受 EvaluationRuleDTO 的实例。如果您执行以下操作会发生什么?

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

EvaluationRuleDTO 是 RuleDTO,因此它必须履行 RuleDTO 定义的合同。

但是,子类中的方法可能会返回 EvaluationRuleDTO 的实例而不是 RuleDTO,因为约定将返回 RuleDTO,而 EvaluationRuleDTO 是 RuleDTO。

于 2012-07-19T21:52:48.307 回答
4

这是因为当你重写一个方法时,你必须使用相同类型或更通用(更广泛)的类型作为参数类型,而不是更窄的类型。

考虑一下。如果你可以使用更窄的类型覆盖一个方法,你会破坏多态能力,你不同意吗?因此,这样做,您将违反JB Nizet 在回答中所说的Liskov 替换原则。

于 2012-07-19T21:48:45.833 回答
4

Java 允许覆盖的返回类型协变,因此您可以将覆盖的返回类型指定为派生更多的类型。但是,Java 不允许覆盖的参数类型协变。

前者安全的原因是您返回的对象将至少具有较少派生类型的功能,因此依赖该事实的客户端仍然能够正确使用返回的对象。

不过,论点并非如此。如果它是合法的,用户可以调用抽象方法并传入一个派生较少的类型(因为这是在抽象类上声明的类型),但是你的派生重写可能会尝试将参数作为派生更多的类型来访问(它不是)导致错误。

理论上,Java 可以允许参数类型逆变,因为这是类型安全的:如果被覆盖的方法只需要一个较少派生的参数,您就不会意外地使用不存在的方法或字段。不幸的是,这目前不可用。

于 2012-07-19T21:49:29.917 回答
0

Java 1.5 具有协变返回类型,这就是它有效的原因

 

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

于 2012-07-19T21:48:56.427 回答
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

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

于 2012-07-19T21:53:25.827 回答
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;
    }
}
于 2016-12-23T18:28:39.690 回答