即使对象是子类,也会调用超类方法

Pra*_*kar 27 java oop polymorphism overriding

我正在玩简单的重载覆盖规则并发现一些有趣的东西.这是我的代码.

package com.demo;

public class Animal {

    private void eat() {
        System.out.println("animal eating");
    }

    public static void main(String args[]) {

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

class Horse extends Animal {
    public void eat() {
        System.out.println("Horse eating");
    }
}
Run Code Online (Sandbox Code Playgroud)

该程序输出如下.

动物吃

这就是我所知道的:

  • 由于我们有private void eat()方法,它不一定会在子类中访问,因此这里不会出现方法覆盖的问题,因为JLS明确定义了它.
  • 现在这不是方法覆盖,它绝对不会public void eat()从Horse类调用方法
  • 现在我们的声明Animal a = new Horse();因多态性而有效.

为什么a.eat()要从Animal类中调用方法?我们正在创建一个Horse对象,为什么要调用Animal类的方法呢?

tem*_*def 24

标记的private方法不能在子类中重写,因为它们对子类不可见.从某种意义上说,你Horse根本就不知道任何这Animal有一个eat方法,因为它被标记private.其结果是,Java不考虑Horseeat方法是一种替代.这主要是作为安全功能设计的.如果一个类有一个标记的方法private,那么假设该方法应该只用于类内部,并且外部世界完全无法访问它.如果子类可以覆盖private方法,那么它可能以意外的方式改变超类的行为,这是(1)不期望的和(2)潜在的安全风险.

因为Java假定private不会覆盖类的方法,所以每当private通过某种类型的引用调用方法时,Java将始终使用引用的类型来确定要调用的方法,而不是使用类型的该引用指向的对象确定要调用的方法.这里,引用是类型的Animal,所以这是被调用的方法,即使该引用指向a Horse.


Adr*_*hum 14

我不确定我是否理解你的困惑.根据您的了解:

你是对的,Horse.eat()不是压倒一切Animal.eat()(因为它是私人的).换句话说,当你打电话时anAnimal.eat(),没有后期绑定发生,因此,你只是在呼唤Animal.eat(),这就是你所看到的.


从你的其他评论来看,似乎你的困惑是编译器决定调用什么.这是一个非常高级的解释:

当编译器看到时 Animal a =...; a.eat();,它将尝试解析要调用的内容.

例如,如果它看到的eat()是一个静态方法,给定a是一个引用Animal,编译器会将其转换为调用Animal.eat().

如果它是一个实例方法,并且它遇到了一个可能被子类覆盖的方法,那么编译器会做的是,它不会生成调用特定方法的指令.相反,它将生成指令以从vtable执行某种查找.从概念上讲,每个对象都有一个小表,其中键是方法签名,值是对实际调用方法的引用.例如,如果在你的情况下,Animal.eat()不是私人的,那么Horse的vtable将包含的是什么["eat()" -> "Horse.eat()"].因此,在运行时,给定一个Animal引用并被eat()调用,实际发生的是:从引用对象的vtable中查找eat(),并调用相关的方法.(如果ref指向a Horse,则关联的方法将是Horse.eat()).这就是大多数情况下后期绑定的魔力.

使用无法覆盖的实例方法,编译器会执行与静态方法类似的操作,并生成直接调用该方法的指令.

(以上在技术上并不准确,只是一个概念性的例子,让你了解发生了什么)

  • 这是非常好的解释:)非常感谢.今天是美好的一天,有些学习 (2认同)

Gho*_*ica 8

您可能在这里忽略的事情:您的主要方法是在Animal类中.因此从同一个类调用private方法eat()是没有问题的.如果将main方法移动到另一个类中,您会发现在Animal上调用eat()会导致编译错误!

当然:如果你在Horse中的eat()上放了@Override注释,你也会收到编译器错误.因为,正如其他人已经很好地解释过的那样:在你的例子中,你并没有覆盖任何东西.

所以,实质上:

  1. 你没有压倒什么
  2. 你没有打电话给你认为你打电话的方法

最后,关于你的评论:当然有一个Animal对象.马正在扩展动物; 所以任何Horse-object也是Animal对象.这就是你能够写下来的原因

Animal a = new Horse();
Run Code Online (Sandbox Code Playgroud)

但重要的是要理解:在那一行之后,编译器不再知道"a" 实际上是一匹马.你宣称"a"为动物; 因此编译器允许您调用Animal声明的方法.请记住:继承基本上是描述"IS-A"关系:在您的示例中,马是动物.