[英]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.我想知道我们是如何得出上述结果的。
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)
.因为变量
c
是Cattle
类型,所以它只知道方法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
:将
c
和h
强制转换为Horse
类型:
((Horse) c).eat((Horse) h);
Output:输出:
Horse eats hay // now `eat(Horse h)` is the most specific method
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:如果您将对象引用更改为
Horse
或Cattle
,如下所示:
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.