通过传递子类的实例调用超类的方法,期望子类实例上的超类参数

Invoking a Method of the Superclass expecting a Superclass argument on a Subclass instance by passing the an instance the of the Subclass

谁能解释一下下面代码的输出结果,这里涉及的Java原理是什么?

class Mammal {
    void eat(Mammal m) {
        System.out.println("Mammal eats food");
    }
}

class Cattle extends Mammal{
    void eat(Cattle c){
        System.out.println("Cattle eats hay");
    }
}

class Horse extends Cattle {
    void eat(Horse h) {
        System.out.println("Horse eats hay");
    }
}

public class Test {
    public static void main(String[] args) {
        Mammal h = new Horse();
        Cattle c = new Horse();
        c.eat(h);
    }
}

它产生以下输出:

Mammal eats food

我想知道我们是如何得出上述结果的。

重载与覆盖

这不是有效的 method overriding,因为所有 方法签名 方法名称 + 参数 )都不同:

void eat(Mammal m)
void eat(Cattle c)
void eat(Horse h)

即调用方法重载(see)和classHorse会有3 不同的方法,没有一个。 IE。它自己 重载 版本 eat()2 继承版本。

编译器会将方法调用 c.eat(h) 映射到最具体的方法,即 eat(Mammal m),因为变量 h 的类型为 Mammal.

为了调用带有签名 eat(Horse h) 的方法,您需要将 h 强制转换为类型 Horse。请注意,此类转换将被视为 so-called narrowing conversion,并且它永远不会自动发生,因为无法保证此类类型转换会成功,因此编译器不会为您完成。

注释掉方法void eat(Mammal m),你会看到编译错误-编译器不执行缩小转换,它只能帮助你widening conversions 因为他们保证成功,因此是安全的。

如果您手动进行类型转换会发生什么:

h 强制转换为类型 Horse:

c.eat((Horse) h);

输出:

Cattle eats hay   // because `c` is of type `Cattle` method `eat(Cattle c)` gets invoked

因为变量 cCattle 类型,它只知道方法 eat(Cattle c) 而不是 eat(Horse h)。在幕后,编译器会将 扩展 h 类型 Cattle.


ch 都强制转换为类型 Horse:

((Horse) c).eat((Horse) h);

输出:

Horse eats hay   // now `eat(Horse h)` is the most specific method

覆盖规则

方法覆盖的规则符合Liskov substitution principle.

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

child class 应该以这样的方式声明它的行为,以便它可以在任何需要 parent 的地方使用:

  • 方法签名 必须完全匹配。 IE。方法名称应与参数类型相同。并且参数应该以相同的顺序声明。

  • 重写方法的访问修饰符可以相同或更宽,但不能更严格。 IE。 parent中的protected方法class可以重写为public也可以保留protected,我们做不到private.

  • Return 重写方法的类型 应该与原始类型 完全相同。但是如果一个 parent 方法声明为 return 一个引用类型,它的子类型可以被 returned。 IE。 if parent returns Number 重写方法可以提供 Integer 作为 return 类型。

  • 如果 parent 方法声明抛出任何 已检查的异常 则允许覆盖的方法声明相同的 异常 或者它们的子类型,可以实现为安全的(或者根本不抛出异常)。不允许使重写方法的安全性低于 parent 声明的方法,即抛出 已检查的异常 未由 parent 方法声明。请注意,关于 运行时异常 (未选中)没有任何限制,重写方法可以自由声明它们,即使它们未由 parent 方法指定。

这将是方法覆盖的有效示例:

static class Mammal{
    void eat(Mammal m){
        System.out.println("Mammal eats food");
    }
}

public class Cattle extends Mammal{
    @Override
    void eat(Mammal c) {
        System.out.println("Cattle eats hay");
    }
}

public class Horse extends Cattle{
    @Override
    public void eat(Mammal h) throws RuntimeException {
        System.out.println("Horse eats hay");
    }
}

main()

public static void main(String[] args) {
    Mammal h = new Horse();
    Cattle c = new Horse();
    c.eat(h);
}

输出:

Horse eats hay

在您的示例中,出现 method overloading(相同的方法名称但传递的参数类型不同)。

当您调用 c.eat(h) 时,编译器会知道您要使用 void eat(Mammal m) 方法,因为您的 h 引用的类型为 Mammal

如果您像这样将对象引用更改为 HorseCattle

Horse h = new Horse();

输出将是:

Cattle eats hay

发生这种情况是因为编译器将根据对象引用类型 Horse.

使用最具体的方法,在本例中为 void eat(Cattle c)

您可能还对使用运行时多态性的 method overriding 感兴趣。