调用超类的方法,通过传递一个子类的实例,期待在子类实例上有超类参数

发布于 2025-02-04 11:31:02 字数 615 浏览 1 评论 0 原文

任何人都可以解释以下代码的输出,这里涉及的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

我想知道我们如何以上述结果进行。

Can anyone please explain the output of the following code, and what's the Java principle involved here?

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

It produces the following output:

Mammal eats food

I want to know how we are coming at the above result.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

南巷近海 2025-02-11 11:31:02

超载与覆盖

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

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

这就是称为 方法过载 请参阅 )类将具有 3 不同的方法,而不是一种。即其自己的超载版本的 eat() 2 继承版本。

编译器将将方法调用 C.EAT(H)映射到最特定的方法,即 eat(Mammal M),因为变量 h 是类型哺乳动物

为了用签名 eat(Horse H)调用该方法,您需要将 h 施加到类型 Horse 中。请注意,这种转换将被视为所谓的 缩小转换 ,它永远不会自动发生,因为不能保证这种类型的演员会成功,因此编译器不会为您做。

评论该方法 void Eat(Mammal M),您将看到编译错误 - 编译器不会执行缩小转换,它只能帮助您使用 /a>,因为它们可以保证成功并因此安全。

如果您要手动进行类型铸造,将会发生什么:

胁迫 h 进入类型 horse

c.eat((Horse) h);

output:

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

因为变量 c c /code>是类型它仅知道方法 eat(cattle c)而不是 eat(Horse H)。在幕后,编译器将 h 延伸到类型


强迫 C H 进入类型

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

输出:

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

覆盖

方法规则 的规则覆盖符合 liskov liskov替代原则

使用指针或引用基本类的函数必须能够在不知道派生类的对象中使用。

儿童班级应以这种方式声明其行为,以便可以在期望其父母的任何地方使用它:

  • 方法签名必须与完全匹配。 IE 方法名称应与参数的类型相同。并且需要按相同的顺序声明参数。重要的是要注意,如果方法签名有所不同(f 或类似于问题中提供的代码段中的实例,其中一种方法的名称已拼写错误),则编译器不知道这些方法是无论如何连接。即,不再认为它是覆盖的案例,方法将被视为不同,并且下面列出的所有其他要求将不适用。这就是为什么强烈建议将@override 注释添加到覆盖方法的原因。有了它,当编译器未能在父类和接口中找到匹配方法时,如果您拼写了名称或以错误的顺序声明参数,则将提供明确的反馈。如果您要求它生成模板(Intellij ctrl> ctrl + o o 注释 >)。

  • 访问修饰符被覆盖方法的可以是相同的或更宽的,但不能更严格。 IE 可以将父类中的受保护方法覆盖为 public ,也可以保留受保护,我们无法将其制作 private> private

  • 原始类型的情况下,被覆盖方法的返回类型应完全相同。但是,如果父方法声明返回参考类型,则可以返回其子类型。即,如果父母返回号码覆盖方法可以提供整数作为返回类型。

  • 如果父级方法声明要投掷任何检查的异常,则允许覆盖方法 可以声明相同的 exceptions 或他们的子类型,也可以可以将其实现为安全( ie根本不抛出异常)。不允许使覆盖方法 不如父母所声明的方法,即抛出检查的例外未通过父方法声明。 注释,关于运行时异常没有限制(未检查),即使未通过父方法指定它们,也可以自由声明它们。

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

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

Overloading vs Overriding

That's not a valid method overriding, because all the method signatures (method name + parameters) are different:

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

That is called method overloading (see) and class Horse will have 3 distinct methods, not one. I.e. its own overloaded version of eat() and 2 inherited versions.

The compiler will map the method call c.eat(h) to the most specific method, which is eat(Mammal m), because the variable h is of type Mammal.

In order to invoke the method with a signature eat(Horse h) you need to coerce h into the type Horse. Note, that such conversion would be considered a so-called narrowing conversion, and it will never happen automatically because there's no guarantee that such type cast will succeed, so the compiler will not do it for you.

Comment out the method void eat(Mammal m) and you will see the compilation error - compilers don't perform narrowing conversions, it can only help you with widening conversions because they are guaranteed to succeed and therefore safe.

That what would happen if you'll make type casting manually:

Coercing h into the type Horse:

c.eat((Horse) h);

Output:

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

Because variable c is of type Cattle it's only aware of the method eat(Cattle c) and not eat(Horse h). And behind the scenes, the compiler will widen the h to the type Cattle.


Coercing both c and h into the type Horse:

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

Output:

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

Rules of Overriding

The rules of method overriding conform to the Liskov substitution principle.

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

The child class should declare its behavior in such a way so that it can be used everywhere where its parent is expected:

  • Method signatures must match exactly. I.e. method names should be the same as well as the types of parameters. And parameters need to be declared in the same order. It is important to note that if method signatures differ (for instance like in the code snippet provided in the question, name of one of the methods was misspelled) the compiler will have no clue that these methods are connected anyhow. I.e. it no longer be considered a case of overriding, methods will be considered to be distinct, and all other requirements listed below will not be applicable. That's why it's highly advisable to add the @Override annotation to the overridden method. With it, the compiler will give a clear feedback when it fails to find a matching method in the parent classes and interfaces, if you've misspelled the name, or declared parameters in the wrong order. Your IDE will add this annotation for you if you ask it to generate a template (shortcut in IntelliJ CTRL + O).

  • The access modifier of an overridden method can be the same or wider, but it can not be more strict. I.e. protected method in the parent class can be overridden as public or can remain protected, we can not make it private.

  • Return type of an overridden method should be precisely the same in case primitive type. But if a parent method declares to return a reference type, its subtype can be returned. I.e. if parent returns Number an overridden method can provide Integer as a return type.

  • If parent method declares to throw any checked exceptions then the overridden method is allowed to declare the same exceptions or their subtypes, or can be implemented as safe (i.e. not throwing exceptions at all). It's not allowed to make the overridden method less safe than the method declared by the parent, i.e. to throw checked exceptions not declared by the parent method. Note, that there are no restrictions regarding runtime exceptions (unchecked), overridden methods are free to declare them even if they are not specified by the parent method.

This would be a valid example of 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
时光沙漏 2025-02-11 11:31:02

在您的示例中,发生(相同的方法名称,但传递了不同的参数类型)。

当您调用 C.EAT(H)时,编译器将知道您要使用 void Eat(Mammal M)方法,因为您的 h h < /代码>参考具有类型哺乳动物

如果您将对象引用更改为 Horse 喜欢这样的:

Horse h = new Horse();

输出将是:发生

Cattle eats hay

这种情况,因为编译器将使用最特定的方法,在这种情况下为基于对象参考类型 Horse ,void eat(牛C)

您可能还对方法覆盖使用运行时多态性。

In your example, method overloading occurs(same method name but different parameter type passed).

When you're calling c.eat(h), the compiler will know that you want to use the void eat(Mammal m) method since your h reference has the type Mammal.

If you would change the object reference to Horse or Cattle like so:

Horse h = new Horse();

The output will be:

Cattle eats hay

This happens because the compiler will use the most specific method, in this case void eat(Cattle c), based on the object reference type Horse.

You may also be interested in method overriding which uses runtime polymorphism.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文