简体   繁体   English

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

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

Can anyone please explain the output of the following code, and what's the Java principle involved here?谁能解释一下以下代码的输出,这里涉及的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);
    }
}

It produces the following output:它产生以下输出:

Mammal eats food

I want to know how we are coming at the above result.我想知道我们是如何得出上述结果的。

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.这称为方法重载请参阅), Horse类将有3不同的方法,而不是一个。 Ie its own overloaded version of eat() and 2 inherited versions.即它自己的eat()重载版本和2继承版本。

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 .编译器会将方法调用c.eat(h)映射到最具体的方法eat(Mammal m) ,因为变量h的类型是Mammal

In order to invoke the method with a signature eat(Horse h) you need to coerce h into the type Horse .为了调用带有签名eat(Horse h)的方法,您需要将h强制转换为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.注释掉void eat(Mammal m)方法,你会看到编译错误 - 编译器不执行缩小转换,它只能帮助你扩大转换,因为它们保证成功并因此安全。

That what would happen if you'll make type casting manually:如果您手动进行类型转换会发生什么:

Coercing h into the type Horse :h强制转换为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) .因为变量cCattle类型,所以它只知道方法eat(Cattle c)而不是eat(Horse h) And behind the scenes, the compiler will widen the h to the type Cattle .在幕后,编译器会将h扩展Cattle类型。


Coercing both c and h into the type Horse :ch强制转换为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 theLiskov substitution principle .方法覆盖的规则符合Liskov 替换原则

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 .方法签名必须完全匹配。 Ie 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 (f or 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.重要的是要注意,如果方法签名不同(f或问题中提供的代码片段中的实例,其中一个方法的名称拼写错误),编译器将不知道这些方法是否已连接。 Ie 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 .这就是为什么强烈建议将@Override注解添加到被覆盖的方法中。 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 ).如果您要求 IDE 生成模板( IntelliJ CTRL + O中的快捷方式),您的 IDE 将为您添加此注释

  • The access modifier of an overridden method can be the same or wider, but it can not be more strict.重写方法的访问修饰符可以相同或更宽,但不能更严格。 Ie protected method in the parent class can be overridden as public or can remain protected , we can not make it private .即父类中的protected方法可以被重写为public或可以保持protected ,我们不能将其设为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.但是如果父方法声明返回引用类型,则可以返回其子类型。 Ie if parent returns Number an overridden method can provide Integer as a return type.即,如果父级返回Number ,则被覆盖的方法可以提供Integer作为返回类型。

  • 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 ( ie not throwing exceptions at all ).如果父方法声明抛出任何已检查的异常,则允许被覆盖的方法声明相同的异常或其子类型,或者可以实现为安全的(即根本不抛出异常)。 It's not allowed to make the overridden method less safe than the method declared by the parent, ie 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

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 .当您调用c.eat(h)时,编译器将知道您要使用void eat(Mammal m)方法,因为您的h引用具有Mammal类型。

If you would change the object reference to Horse or Cattle like so:如果您将对象引用更改为HorseCattle ,如下所示:

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 .发生这种情况是因为编译器将使用最具体的方法,在本例中为void eat(Cattle c) ,基于对象引用类型Horse

You may also be interested in method overriding which uses runtime polymorphism.您可能还对使用运行时多态性的方法覆盖感兴趣。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM