谁能解释一下下面代码的输出,以及这里涉及的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,因为所有method signatures(method name + parameters)都不同:

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

这称为105(see),类Horse将有3个不同的方法,而不是一个.一、 e.eat()2继承版本的overloaded版本.

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

为了使用签名eat(Horse h)调用该方法,需要将h强制为类型Horse.请注意,这种转换将被视为所谓的narrowing conversion,而且它永远不会自动发生,因为不能保证这种类型转换会成功,所以编译器不会为您执行转换.

注释掉方法void eat(Mammal m),您将看到编译错误-编译器不执行narrowing conversions,它只能帮助您执行widening conversions,因为它们保证会成功,因此是安全的.

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

h强制为Horse型:

c.eat((Horse) h);

Output:

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

因为变量c的类型是Cattle,所以它只知道方法eat(Cattle c),而不知道方法eat(Horse h).在幕后,编译器会将h转换为Cattle类型.


ch强制为Horse型:

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

Output:

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

覆盖规则

method overriding的规则符合Liskov substitution principle.

使用指向基类的指针或引用的函数必须能够在不知情的情况下使用派生类的对象.

子类应该以这样的方式声明其行为,以便可以在需要父类的任何地方使用它:

  • Method signatures必须与exactly匹配.一、 e.方法名称应与参数类型相同.和参数应按相同的顺序声明.

  • 重写方法的access modifier可以相同或更宽,但不能更严格.一、 父类中的protected方法可以重写为public,也可以保留为protected,但我们不能将其改为private.

  • 在 case primitive type中,重写方法的Return type应该完全相同.但如果父方法声明返回引用类型,则可以返回其子类型.一、 e.如果父级返回Number,则重写的方法可以提供Integer作为返回类型.

  • 如果父方法声明抛出任何checked exceptions,则允许重写的方法声明相同的exceptions或其子类型,可以实现为安全的(或根本不抛出异常).不允许使重写方法的安全性低于父方法声明的方法,即抛出checked exceptions个未由父方法声明的方法.注意,runtime exceptions没有限制(未选中),被重写的方法可以自由声明它们,即使它们不是由父方法指定的.

这是method overriding的一个有效示例:

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);
}

Output:

Horse eats hay

Java相关问答推荐

@ EnableRouting注释在Kotlin项目中不工作

如何将kotlin代码转换为java

我的scala文件失败了Scala.g4 ANTLR语法

强制Mockito返回null而不是emtpy list

Intellij显示项目语言级别最高为12,尽管有java版本17 SDK

如何配置ActiveMQ Artemis以使用AMQP 1.0和其他协议与Java

Java .类参数不通过构造函数传递

Java中是否有某种类型的池可以避免重复最近的算术运算?

扩展到弹出窗口宽度的JavaFX文本字段

如何才能使我的程序不会要求两次输入?

将关键字与正文中的_Allowed匹配,但带有__Signing可选后缀

基于调车场算法的科学计算器

查找剩余的枚举

我可以在MacOS上使用什么Java函数来在适当的设备上以适当的音量播放适当的alert 声音?

Sack()步骤中的合并运算符未按预期工作

将JSON字符串转换为Java类

Cordova Android Gradle内部版本组件不兼容

在应用程序运行时更改LookAndFeel

java21预览未命名的符号用于try-with-resources

[Guice/MissingImplementation]:未绑定任何实现