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

Superclass method being called even though object is of subclass

我在尝试简单的重载覆盖规则并发现了一些有趣的东西。这是我的代码。

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");
    }
}

这个程序输出如下。

animal eating

这是我所知道的:

为什么 a.eat()Animal class 调用方法?我们正在创建一个 Horse 对象,那么为什么要调用 Animal class' 方法?

标记为 private 的方法无法在子class 中覆盖,因为它们对子class 不可见。从某种意义上说,您的 Horse class 根本不知道 Animal 有一个 eat 方法,因为它被标记为 private。因此,Java 不会将 Horseeat 方法视为重写。这主要是作为一项安全功能而设计的。如果 class 有一个标记为 private 的方法,则假定该方法应该仅用于 class 内部,并且外部世界完全无法访问它。如果 subclass 可以覆盖 private 方法,那么它可能会以意想不到的方式改变 superclass 的行为,即 (1) 不是预期的和 (2)潜在的安全风险。

因为 Java 假设 class 的 private 方法不会被重写,所以每当您通过某种类型的引用调用 private 方法时, Java 将始终使用 reference 的类型来确定调用哪个方法,而不是使用该 reference[=] 指向的 object 的类型29=] 来判断要调用的方法。此处,引用的类型为 Animal,因此这就是被调用的方法,即使该引用指向 Horse.

不知道我是否理解你的困惑。根据您的了解:

你是对的,Horse.eat() 并没有覆盖 Animal.eat()(因为它是私有的)。换句话说,当您调用 anAnimal.eat() 时,不会发生后期绑定,因此,您只是调用 Animal.eat(),这就是您所看到的。


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

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

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

如果是实例方法,遇到可能被子方法覆盖的方法class,编译器做的是,不会生成调用具体方法的指令。相反,它将生成指令以从 vtable 进行某种查找。从概念上讲,每个对象都会有一个小 table,其中键是方法签名,值是对实际要调用的方法的引用。例如,如果在您的情况下,Animal.eat() 不是私有的,那么 Horse 的 vtable 将包含类似 ["eat()" -> "Horse.eat()"] 的内容。因此,在 运行时 ,给定一个 Animal 引用并调用 eat(),实际上发生的是:从引用对象的 vtable 中查找eat(),并调用关联的方法。 (如果 ref 指向 Horse,关联的方法将是 Horse.eat() )。在大多数情况下,这就是后期绑定的神奇之处。

对于无法重写的实例方法,编译器会执行与静态方法类似的操作,并生成直接调用该方法的指令。

(以上内容在技术上并不准确,只是一个概念性的说明,供您了解发生了什么)

无法重写私有方法。

http://ideone.com/kvyngL

/* package whatever; // don't place package name! */

import java.util.*;
import java.lang.*;
import java.io.*;

class Animal {

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

class Horse extends Animal {
    public void eat() {
        System.out.println("Horse eating");
    }
}
/* Name of the class has to be "Main" only if the class is public. */
class Ideone
{
    public static void main (String[] args) throws java.lang.Exception
    {
        // your code goes here
                Animal a = new Horse();
        a.eat();

    }
}

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

当然:如果您将 @Override 注释放在 Horse 中的 eat() 上,您也会收到编译器错误。因为,正如其他人很好地解释的那样:您没有覆盖示例中的任何内容。

所以,本质上:

  1. 你没有覆盖任何东西
  2. 您没有调用您认为正在调用的方法

最后,关于您的评论:当然有一个 Animal 对象。马是延伸的动物;所以任何 Horse 对象也是 Animal 对象。这就是为什么你能够写下

Animal a = new Horse();

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

简而言之,您在 Java 中重载了 "overriding" 的预期含义 :-)。

让我们假设其他人写了 Animal class:(稍微重写它,不改变任何语义,但为了展示一个好的实践)。我们还将假设 Animal 编译并运行良好:

public class Animal {

    public static void main(String args[]) {

        Animal a = new Animal(); // yes, Animal, no Horse yet.
        a.eat();
    }
    ///// Animal's private methods, you should not look here
    private void eat() {
        System.out.println("animal eating");
    }
    ///// Animal's private methods, you should not look here
}

这是一个很好的 Java 编码实践,因为 Animal class 的作者不希望你,reader 真正了解任何东西关于 Animal 的私事。

接下来,您查看 Animalpublic static void main 方法并正确推断那里定义了一个名为 eat() 的方法。此时,以下内容成立:

  1. 与 Java 中的任何其他 class 一样,Animal 扩展了 Object
  2. 你查看Object的public(和protected)方法,发现没有eat()这样的方法。鉴于 Animal 编译正常,您可以推断 eating 一定是 Animal 的私事! Animal 没有其他方法可以编译。因此,不看Animal的私有业务,你可以推断Animalclass中有一个eat()方法,即private[=73] =]!

现在假设您的意图是创造另一种名为 Horse 的动物作为专门的 Animal 并赋予它特殊的进食行为。您认为您 不会 去研究 Java Lang Spec 并找出这样做的所有规则,然后只需使用 extends 关键字即可完成它。然后出现了 Horse 的第一个版本。然而,您在某处听说,最好阐明您 overriding 的意图(这是您现在确定的一件事——您确实想要 override eatHorse 的行为):

class Horse extends Animal {
    @Override
    public void eat() {
        System.out.println("Horse eating");
    }
}

对;您添加标签 @Override。诚然,这总是一个好主意,但会增加冗长的措辞(这是一个很好的做法,原因有几个,我们不会在这里讨论)。

你尝试编译 Horse.java 你会看到:

Error:(21, 5) java: method does not override or implement a 
method from a supertype

因此,比我们更了解 Java 编程语言的编译器告诉我们,我们实际上 不是 覆盖或 实现超类型 中声明的方法。

现在 Java 中的覆盖处理对我们来说变得更加清晰。由于我们应该只覆盖为覆盖而设计的行为,即 public 和受保护的方法,因此我们应该注意 superclasses 的编写方式。在这种情况下,无意中,显然是为扩展而设计的 superclass Animal 使 subclass 无法覆盖 eating 行为!

即使我们删除了 @Override 标记以摆脱编译器的技术性 error/warning,我们也不一定会做正确的事情,因为正如您所观察到的,在运行时,签名匹配的意外方法被调用。那更糟。

您写道:

Animal a = new Horse();

在这种情况下 a 指向 Horse 对象就好像它是 Animal[=24= 的对象一样] 类型。

a.eat()Animal class 中是私有的,所以它不能被覆盖,这就是为什么 a.eat() 来自 动物