具有相同名称调用的非重写子类方法

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 实现。