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

时间:2016-08-22 06:03:43

标签: java oop polymorphism override

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

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

该程序输出以下内容。

  

动物吃

以下是我所知道的:

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

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

6 个答案:

答案 0 :(得分:24)

标记为private的方法无法在子类中重写,因为它们对子类不可见。从某种意义上说,Horse班级不知道Animaleat方法,因为它标记为private。因此,Java不会将Horse的{​​{1}}方法视为覆盖。这主要是作为安全功能设计的。如果一个类有一个标记为eat的方法,那么假设该方法应该只用于类内部,并且外部世界完全无法访问它。如果子类可以覆盖private方法,那么它可能会以意外的方式改变超类的行为,这是(1)不期望的和(2)潜在的安全风险。

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

答案 1 :(得分: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(),实际发生的是:使用eat()从引用对象的vtable中查找,并调用相关的方法。 (如果引用指向Horse,则关联的方法将为Horse.eat())。这就是大多数情况下后期绑定的魔力。

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

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

答案 2 :(得分:8)

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

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

所以,实质上:

  1. 你没有压倒任何事情
  2. 您没有调用您认为正在调用的方法
  3. 最后,关于你的评论:当然有一个动物对象。马是延伸动物;所以任何Horse-object也是Animal对象。这就是你能够写下来的原因

    Animal a = new Horse();
    

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

答案 3 :(得分:3)

简而言之,您正在超载Java中“覆盖”的预期含义:-)。

让我们假装其他人写了Animal类:(稍微重写一下,不改变任何语义,但要展示一个好的做法)。我们还假设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类的作者不希望您(该代码的读者)真正了解Animal的私有企业。

接下来,您查看public static void main的{​​{1}}方法,并正确地推断出在那里定义了一个名为Animal的方法。此时,以下是:

  1. 与Java中的任何其他类一样,eat()扩展Animal
  2. 您查看Object的公共(和受保护)方法,发现没有Object这样的方法。鉴于eat()编译正常,您可以推断Animal必须是eat的私人企业! Animal没有其他方法可以编译。因此,在不查看Animal私人业务的情况下,您可以推断Animal类中的eat()方法私有
  3. 现在让我们假设您的目的是创建另一个名为Animal的动物作为专门的Horse并给它一种特殊的进食行为。您认为您将查看Java Lang Spec并找出执行此操作的所有规则,并使用Animal关键字并完成它。然后出现extends的第一个版本。你听说过某个地方,最好澄清一下覆盖的意图(这是你现在确定的一件事 - 你确实希望覆盖 Horse行为eat):

    Horse

    右;您添加了标记class Horse extends Animal { @Override public void eat() { System.out.println("Horse eating"); } } 。无可否认,这总是一个好主意,增加的措辞(这是一个很好的做法,有几个原因,我们不会在这里讨论)。

    您尝试编译@Override并看到:

    Horse.java

    因此,比我们更了解Java编程语言的编译器告诉我们,事实上,我们重写或实现声明的方法超类型

    现在,Java中的覆盖处理对我们来说变得更加清晰。因为我们应该只覆盖那些为覆盖而设计的行为,即public和protected方法,所以我们应该注意如何编写超类。在这种情况下,无意中,超类Error:(21, 5) java: method does not override or implement a method from a supertype 显然是为扩展而设计的,这使得子类无法覆盖Animal行为!

    即使我们删除了eat标记以摆脱编译器错误/警告的技术性,我们也不一定会做正确的事情,因为在运行时,正如您所观察到的那样,非预期的方法的签名匹配,被调用。更糟糕的是。

答案 4 :(得分:0)

无法覆盖私有方法。

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();

    }
}

答案 5 :(得分:0)

您写道:

Animal a = new Horse();

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

a.eat()动物类中是私有的,因此无法覆盖,这就是a.eat()来自动物

的原因