具有相同名称调用的非重写子类方法
Non-overridden subclass method with same name calling
class Animal{
void eat(Animal animal){
System.out.println("animal eats animal");
}
}
public class Dog extends Animal{
void eat(Dog dog){
System.out.println("dog eats dog");
}
public static void main(String[] args) {
Animal a = new Dog();
Dog b = new Dog();
a.eat(b);
b.eat(b);
}
}
在上面的代码中,输出将是
animal eats animal
dog eats dog
为什么会这样?
它基于继承和多态的概念
当 sub-class 具有与 superclass 相同的签名方法时,就会发生覆盖。在你的代码中,在下面的 subclass * 方法中,传递的参数是一个 Dog
类型的对象,在 superclass 即 Animal
中,传递的参数是一个Animal
类型对象。
void eat(Dog dog){
System.out.println("dog eats dog");
}
所以你可以把上面的方法改成下面这样,看看覆盖效果:-
void eat(Animal dog){
System.out.println("dog eats dog");
}
正如@Mat 所建议的那样,最好使用@Override 注释,因为它将帮助java 编译器在编译时自己发现问题。
下面我将尝试解释继承和多态性如何在您更改 Dog
class 中的 eat
方法的签名后工作的概念:-
继承是一种将一个 class 建立在另一个 class 之上的方法,就像从现有模板构建模板一样。您可以创建一个名为 'Dog' 的 class 作为所有 Dog 对象的模板。然后我们可以创建另一个名为 'Animal' 的 class,它是 'Dog' class 的父 class。所有的狗都是动物,但并非所有的动物都是狗。我们的 Animal class 可以为所有 Animals 定义功能,然后 Dog class 可以从 Animal class 获取所有这些功能,而无需重写它。 Dog class 然后可以添加仅特定于 Dog 对象的更多功能、更多变量和方法。
狗class继承了动物class,这就是继承。 Dog class 正在覆盖 Animal class eat 方法。
当我们说 Animal a = new Dog();
时,我们声明了一个变量 a
,它被声明为 Animal 类型,但它被初始化为 Dog 对象。这就是多态。因为 Dog 是从 Animal class 扩展而来的,所以我们可以把它当作 Animal 来对待,并声明为 Animal 变量类型。我们不能反过来,因为 Animal class 不是从 Dog class 延伸出来的(不是所有的 Animals 都是狗)
这是因为 a
变量被视为 Animal 数据类型。这就是为什么您将能够访问所有 Animal
class 方法,但与 Dog
具有相同签名的方法将被 Dog
class。请记住,等号'='的左侧是声明,右侧是初始化。
此外,这就是为什么当您声明 Dog b = new Dog();
并调用 eat()
方法时,它会从 Class Dog
而不是 Animal
调用实现它被明确提到为对象类型 Dog
class Animal{
void eat(Animal animal){
System.out.println("animal eats animal");
}
}
public class Dog extends Animal{
void eat(Dog dog){
System.out.println("dog eats dog");
}
public static void main(String[] args) {
Animal a = new Dog(); //We use this when we don't know the exact runtime type of an object
//Parent can hold any child but only parent specific methods will be called.
Dog b = new Dog();
a.eat(b); //Parent method will be called i.e Animal.eat(...)
b.eat(b); //Dog Class method will be called i.e Dog.eat(...)
}
}
您可能希望看到两次“dog eats dog”。这不会发生,因为这两种方法具有不同的签名。因此,Dog#eat(Dog)
不会覆盖 Animal#eat(Animal)
,而是提供更具体的 eat
方法。
如果将@Override
添加到void eat(Dog dog)
,将会出现错误。使用此注释是一种很好的做法,因为它表示带注释的方法应该覆盖超类型中的方法声明。如果该方法不这样做(如您的示例所示),您会收到以下错误以提醒您是否这样做:
Method does not override method from its superclass
如果要覆盖Dog
中的eat
方法,需要提供相同的签名:
@Override
void eat(Animal animal) { // instead of eat(Dog dog)
System.out.println("dog eats dog");
}
只是因为Java不支持contravariant parameters。另一方面,它支持协变 return 类型。
由于支持协变 return 类型,subclass 覆盖可以在层次结构中具有更具体的 return 类型,同时覆盖基础 class 方法,如下面的代码有效:
class Animal {
protected Animal getAnimal() {
System.out.println("Animal");
return this;
}
}
class Dog extends Animal {
@Override
protected Dog getAnimal() {
System.out.println("Dog");
return this;
}
}
在上面的示例中,您可以观察到 Dog.getAnimal()
return 是一个更具体的 Dog
而不是基础 Animal
但它仍然被认为是覆盖,因为 Java 支持协变 return 类型。
另一方面,如果您使用参数执行此操作:
class Animal {
protected void petAnimal(Animal animal) {
System.out.println("Petting Animal");
}
}
class Dog extends Animal {
@Override
protected void petAnimal(Dog dog) {
System.out.println("Petting Dog");
}
}
这不是覆盖,而是过载。
因此,petAnimal()
方法(一个以 Animal
作为参数,另一个以 Dog
作为参数)被视为两种不同的方法。请记住,参数是方法签名的一部分,而 return 类型不是。
第二个示例甚至不起作用,因为 @Override
注释发现该方法不是覆盖。每当您想确保覆盖时,请使用 @Override
注释,如果您没有覆盖该方法,它会通知您。 @Override
实现接口时也可以使用
首先,eat()
未在子 class Dog
中被覆盖
通常方法重载不一定需要继承,可以在同一个class内实现。但是,在此代码中 eat()
方法被子 class Dog
.
重载
Overloaded methods are differentiated by the number and the type of the arguments passed into the method.
因此,在编译时它总是根据其类型选择最具体的 class 实现。
class Animal{
void eat(Animal animal){
System.out.println("animal eats animal");
}
}
public class Dog extends Animal{
void eat(Dog dog){
System.out.println("dog eats dog");
}
public static void main(String[] args) {
Animal a = new Dog();
Dog b = new Dog();
a.eat(b);
b.eat(b);
}
}
在上面的代码中,输出将是
animal eats animal
dog eats dog
为什么会这样?
它基于继承和多态的概念
当 sub-class 具有与 superclass 相同的签名方法时,就会发生覆盖。在你的代码中,在下面的 subclass * 方法中,传递的参数是一个 Dog
类型的对象,在 superclass 即 Animal
中,传递的参数是一个Animal
类型对象。
void eat(Dog dog){
System.out.println("dog eats dog");
}
所以你可以把上面的方法改成下面这样,看看覆盖效果:-
void eat(Animal dog){
System.out.println("dog eats dog");
}
正如@Mat 所建议的那样,最好使用@Override 注释,因为它将帮助java 编译器在编译时自己发现问题。
下面我将尝试解释继承和多态性如何在您更改 Dog
class 中的 eat
方法的签名后工作的概念:-
继承是一种将一个 class 建立在另一个 class 之上的方法,就像从现有模板构建模板一样。您可以创建一个名为 'Dog' 的 class 作为所有 Dog 对象的模板。然后我们可以创建另一个名为 'Animal' 的 class,它是 'Dog' class 的父 class。所有的狗都是动物,但并非所有的动物都是狗。我们的 Animal class 可以为所有 Animals 定义功能,然后 Dog class 可以从 Animal class 获取所有这些功能,而无需重写它。 Dog class 然后可以添加仅特定于 Dog 对象的更多功能、更多变量和方法。
狗class继承了动物class,这就是继承。 Dog class 正在覆盖 Animal class eat 方法。
当我们说 Animal a = new Dog();
时,我们声明了一个变量 a
,它被声明为 Animal 类型,但它被初始化为 Dog 对象。这就是多态。因为 Dog 是从 Animal class 扩展而来的,所以我们可以把它当作 Animal 来对待,并声明为 Animal 变量类型。我们不能反过来,因为 Animal class 不是从 Dog class 延伸出来的(不是所有的 Animals 都是狗)
这是因为 a
变量被视为 Animal 数据类型。这就是为什么您将能够访问所有 Animal
class 方法,但与 Dog
具有相同签名的方法将被 Dog
class。请记住,等号'='的左侧是声明,右侧是初始化。
此外,这就是为什么当您声明 Dog b = new Dog();
并调用 eat()
方法时,它会从 Class Dog
而不是 Animal
调用实现它被明确提到为对象类型 Dog
class Animal{
void eat(Animal animal){
System.out.println("animal eats animal");
}
}
public class Dog extends Animal{
void eat(Dog dog){
System.out.println("dog eats dog");
}
public static void main(String[] args) {
Animal a = new Dog(); //We use this when we don't know the exact runtime type of an object
//Parent can hold any child but only parent specific methods will be called.
Dog b = new Dog();
a.eat(b); //Parent method will be called i.e Animal.eat(...)
b.eat(b); //Dog Class method will be called i.e Dog.eat(...)
}
}
您可能希望看到两次“dog eats dog”。这不会发生,因为这两种方法具有不同的签名。因此,Dog#eat(Dog)
不会覆盖 Animal#eat(Animal)
,而是提供更具体的 eat
方法。
如果将@Override
添加到void eat(Dog dog)
,将会出现错误。使用此注释是一种很好的做法,因为它表示带注释的方法应该覆盖超类型中的方法声明。如果该方法不这样做(如您的示例所示),您会收到以下错误以提醒您是否这样做:
Method does not override method from its superclass
如果要覆盖Dog
中的eat
方法,需要提供相同的签名:
@Override
void eat(Animal animal) { // instead of eat(Dog dog)
System.out.println("dog eats dog");
}
只是因为Java不支持contravariant parameters。另一方面,它支持协变 return 类型。 由于支持协变 return 类型,subclass 覆盖可以在层次结构中具有更具体的 return 类型,同时覆盖基础 class 方法,如下面的代码有效:
class Animal {
protected Animal getAnimal() {
System.out.println("Animal");
return this;
}
}
class Dog extends Animal {
@Override
protected Dog getAnimal() {
System.out.println("Dog");
return this;
}
}
在上面的示例中,您可以观察到 Dog.getAnimal()
return 是一个更具体的 Dog
而不是基础 Animal
但它仍然被认为是覆盖,因为 Java 支持协变 return 类型。
另一方面,如果您使用参数执行此操作:
class Animal {
protected void petAnimal(Animal animal) {
System.out.println("Petting Animal");
}
}
class Dog extends Animal {
@Override
protected void petAnimal(Dog dog) {
System.out.println("Petting Dog");
}
}
这不是覆盖,而是过载。
因此,petAnimal()
方法(一个以 Animal
作为参数,另一个以 Dog
作为参数)被视为两种不同的方法。请记住,参数是方法签名的一部分,而 return 类型不是。
第二个示例甚至不起作用,因为 @Override
注释发现该方法不是覆盖。每当您想确保覆盖时,请使用 @Override
注释,如果您没有覆盖该方法,它会通知您。 @Override
实现接口时也可以使用
首先,eat()
未在子 class Dog
通常方法重载不一定需要继承,可以在同一个class内实现。但是,在此代码中 eat()
方法被子 class Dog
.
Overloaded methods are differentiated by the number and the type of the arguments passed into the method.
因此,在编译时它总是根据其类型选择最具体的 class 实现。