2016-08-22 131 views
27

我正在玩弄简单的重载覆盖规则并发现了一些有趣的内容。这是我的代码。即使对象是子类也会调用超类方法

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明确地定义了它。
  • 现在,这不是方法重载,它是绝对不会从马类
  • 现在我们的声明Animal a = new Horse();是因为多态性的有效调用public void eat()方法。

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

+0

嘛,你不解释它的正上方。 'a'的声明类型是Animal,而不是Horse,而Animal.eat()是私有的,所以它不能被覆盖,所以它不能被多态地调用。子类方法将被调用,如果它覆盖基类的方法类,但它不,所以... –

+1

@JBNizet,是的,但他们为什么动物类吃方法被称为,我无法理解。是因为我们有参考动物类吗?但是我们有Horse类的对象。那为什么要调用Animal类的方法? –

+4

所有编译器都知道'a'是它是一个Animal:这是它的声明类型。所以编译器在Animal中寻找eat()方法。它找到一个,它是私有的,所以它调用这个方法。并且由于它不可能被覆盖,因此正在调用Animal.eat()。 –

回答

14

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

你是对的,Horse.eat()不是重写Animal.eat()(因为它是私人的)。换句话说,当你打电话给anAnimal.eat()时,不会发生后期绑定,因此,你只需调用Animal.eat(),这就是你所看到的。


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

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

例如,如果它认为eat()是一个静态方法,给定aAnimal的引用,编译器将它传送到调用Animal.eat()

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

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

(上面没有技术上准确,只是一个概念图,让你明白发生了什么)

+0

谢谢,我现在明白:)时间学习方法绑定:) –

+2

多数民众赞成在很好的解释:)非常感谢。今天是一个美好的一天,得到一些学习 –

+0

正确的我如果错了,基本上'方法'从编译器(编译时间)获取传递标志,并且因为没有运行时错误,所以一切(**访问修饰符**)在运行时,因此简单的方法调用发生?和'eat()'在'Animal'中是私有的,所以执行从'Animal'开始? – emotionlessbananas

24

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

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

+1

非常感谢您的回复。我明白,私人方法不会被覆盖,我已经提到了问题本身。 Horse方法不会被调用,因为它不是覆盖。我唯一的问题是为什么动物类方法被称为即使它是马类对象。 –

+0

@PrasadKharkar我想你可能想看看如何实现方法的后期绑定(通过v-表)。高层次的理解应该足以让你了解行为 –

+0

@PrasadKharkar我想我明白你对此感到困惑。我相应地更新了我的答案 - 如果您仍然有任何问题,请告诉我! – templatetypedef

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

    } 
} 
+2

这里的代码看起来与问题中的代码非常相似。它有什么不同吗? – templatetypedef

+0

不,我想显示java编译器返回的错误,告诉私人吃方法不能被覆盖。 –

+0

@templatetypedef main()'在其他类 – emotionlessbananas

8

,你很可能俯瞰这里的东西:你的主要方法是动物类中。因此,从同一个类调用私有方法eat()是没有问题的。如果你将你的主要方法移动到另一个类中,你会发现调用eat()在动物将导致编译器错误!

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

因此,从本质:

  1. 你没有覆盖任何东西
  2. 你没有调用该方法,你以为你叫

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

Animal a = new Horse(); 

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

+0

是的,这是真的,我理解它。我唯一的困惑是,即使没有动物的对象,动物类的吃法也叫做。这是我的疑问。它是否仅仅因为我们有Animal类型的引用变量而调用Animal类方法?感谢, –

+0

得到解决疑惑。真的很感激它。 –

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方法,并正确推断在此定义了一个名为eat()的方法。在这一点上,下面的搁置:

  1. 像Java中的任何其他类一样,Animal延伸Object
  2. 您看看Object的公共(和受保护)方法,并发现没有像eat()这样的方法。鉴于Animal编译得很好,你可以推断eat ing必须是Animal的私人业务! Animal无法编译。因此,在不看Animal的私人业务的情况下,您可以推断Animal类中有eat()方法,即私人

现在,让我们说你的目的是要创建一个名为Horse另一种动物作为一个专门Animal,并给它吃的特殊行为。你想知道你是而不是要去查看Java Lang Spec并找出所有这些规则,只需使用extends关键字并完成它。然后出现Horse的第一个版本。然而地方,它是更好地阐明你的覆盖的意图你听说过(这是一件事,你现在是肯定的 - 你想覆盖eat荷兰国际集团的Horse行为):

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中重写的处理变得更加清晰了。由于我们应该仅仅覆盖那些被设计用于重写的行为,即公共方法和受保护的方法,所以我们应该小心如何编写超类。在这种情况下,无意中,显然设计用于扩展的超类Animal使得子类不可能覆盖行为!

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

+0

这是一个很好的答案吉达,非常感谢 –

0

您写道:

Animal a = new Horse(); 

在这种情况下一个对象,就好像它是动物类型的对象。

a.eat()是私人在动物类,所以它不能被重写,这就是为什么a.eat()来自动物

相关问题