重载和重写方法中的多态性

Posted

技术标签:

【中文标题】重载和重写方法中的多态性【英文标题】:Polymorphism in Overloaded and Overridden Methods 【发布时间】:2017-06-06 05:38:31 【问题描述】:

让我们来看看这个简单的 Java 代码:

public class Animal 
  public void eat() 
    System.out.println("Generic Animal Eating Generically");
   


public class Horse extends Animal 
  public void eat() 
    System.out.println("Horse eating hay ");
  

  public void eat(String s) 
    System.out.println("Horse eating " + s);
  

我正在尝试找出三个eat() 方法的哪个版本将运行。现在,当我输入时

Animal a = new Animal();
a.eat();

输出是“Generic Animal Eating Generically”,完全可以理解。

当我输入时也会发生同样的事情:

Horse h = new Horse();
h.eat();

输出是“马吃干草”,这也是完全合乎逻辑的。

这就是让我感到困惑的地方。当我输入时:

Animal ah = new Horse();
ah.eat();

我明白了:

Horse eating hay

我希望编译器从 Animal 类引用而不是 Horse 对象引用调用eat() 方法。

所以我的问题是,当我有一个泛型引用变量时,我如何才能确定编译器将调用哪个方法 引用对象类型的类型(例如:Animal horse = new Horse();

【问题讨论】:

【参考方案1】:

我希望编译器从 Animal 类引用而不是 Horse 对象引用调用eat() 方法。

让我们先纠正这个说法。变量ahAnimal 类型的引用,而new Horse() 语句创建Horse 类型的实例并将其分配给Animal 引用。

现在术语已经很清楚了,这种行为是预期的,被称为运行类型多态性或动态方法分派。在编译时,eat() 是根据 Animal 类型的引用类型解析的,但在运行时,将调用的方法是基于 Horseinstance 类型.

当我有一个引用对象类型的泛型引用变量类型时,我如何确定编译器将调用哪个方法

您可以按照以下简单步骤操作:

    检查被调用的方法。 ah.eat() 正在调用方法 eat。 查看父类和子类中是否存在具有完全相同签名(返回类型协方差除外)的方法。 (方法是否被覆盖?) 检查引用类型。在Animal ah = new Horse() 中,引用类型为Animal 即父类 检查实例类型。在Animal ah = new Horse() 中,实例类型为Horse,即子类。

如果以上所有条件都满足,您正在查看运行类型多态性,并且将调用子类中的方法。在任何其他情况下,将根据引用类型来解析要调用的方法。

理解子类从其父类继承方法也是值得的。假设您从 Horse 类中删除了 public void eat() 方法,您不再是 Overrding eat() 方法;然而,Horse 中的public void eat(String s) 方法仍然被称为重载Animal 继承的eat 方法。接下来,让我们在Animal 中添加一个public void eat(String s) 方法。通过此添加,您现在可以重载 Animal 中的eat 方法,并重载 Horse 类中的方法。无论您如何更改代码,上面提到的 4 个步骤将始终帮助您决定将调用哪个方法。

【讨论】:

您的帖子确实很有帮助,但请您回答这两个问题。 1)从条件(2)开始,如果我在父类和子类中有一个重载的方法(而不是一个覆盖的方法)怎么办?那么引用会决定调用哪个方法吗?我的第二个问题是我们还有哪些其他可能的情况(从你的最后一句话)?非常感谢,很抱歉让我的问题给您带来负担。 (顺便说一句,我选择了你的答案)。 没关系。我刚刚发现引用类型决定了调用哪个重载方法。但是当我们有一个被覆盖的方法时,它是决定哪一个的对象。非常感谢,很抱歉给您带来麻烦。 @JesseJames 添加了一个新段落,让事情变得更加混乱。您可以使用段落中建议的更改来测试我提到的 4 个步骤,您会发现它们始终适用:) 投反对票的人能否发表评论并解释我的回答有什么问题。请不要沉迷于对 Stack Overflow 的仇恨投票,因为这违反了本网站的礼仪。让我们像在“现实生活”中一样对此进行文明讨论?【参考方案2】:

这在 Java 中称为动态绑定。使用显式对象类型而不是引用类型。

无法使用单个方法调用被覆盖的超级方法和覆盖的方法,请参阅:How to call the overridden method of a superclass。您可以为您的马添加一个方法,该方法将调用委托给动物,例如:

public class Horse extends Animal 
    public void animalEat() 
        super.eat();
    

    public void eat() 
        System.out.println("Horse eating hay ");
    

【讨论】:

无法调用被覆盖的超级方法。我不完全遵循这一点。当您可以在 eat 方法本身中使用 super.eat() 时,为什么还需要单独的 animalEat 方法? @CKing 你肯定可以在覆盖的方法中实现它,但是你总是调用超级,也许你不想要那个。通常,当您想调用覆盖方法(其中没有超级方法)时,您会实现它。对于需要访问 super 方法的极少数情况,您可以添加 animalEat 方法 - 所以这是一种增强。 我明白你在说什么。但是语句不可能调用被覆盖的超级方法是不正确的,因为通过super总是可以做到这一点。您可能的意思是不可能使用单个方法调用覆盖的超级方法和覆盖的方法。【参考方案3】:

这是由于方法覆盖而发生的。在方法覆盖中,引用类型无关紧要,重要的是对象类型。 Animal ah 只是对对象的引用,而实际对象的类型是 Horse。因此,Horse 的方法将被调用,而不是引用类型 Animal 的方法。

【讨论】:

【参考方案4】:

好的, new 关键字将创建给定类的实例...

new Horse();

现在 Horse 类已经是 Animal 的子级。所以下面将被实例化。

public void eat() 
  System.out.println("Horse eating hay ");

现在您正尝试将该对象存储在 Animal 的对象中。 这意味着马的对象存储在动物的对象中。

Animal ah = new Horse();

所以在 Animal 的对象中 Horse 的成员已经被存储了。 这就是编译器打印子类方法值的原因。

【讨论】:

以上是关于重载和重写方法中的多态性的主要内容,如果未能解决你的问题,请参考以下文章

40-重载和重写的区别?

java中重载,继承,重写和多态的区别

Java中重载和重写的区别

Java中的多态

php 方法重写和多态的区别

java语言中,overload(重载)和override(覆盖)有何区别?