我正在玩简单的重载覆盖规则并发现一些有趣的东西。这是我的代码。
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明确定义了它。public void eat()
方法Animal a = new Horse();
因多态性而有效。为什么a.eat()
从Animal
类调用方法?我们正在创建一个Horse
对象,那么为什么Animal类'方法被调用?
答案 0 :(得分:24)
标记为private
的方法无法在子类中重写,因为它们对子类不可见。从某种意义上说,Horse
班级不知道Animal
有eat
方法,因为它标记为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注释,你也会收到编译器错误。因为,正如其他人已经很好地解释过的那样:在你的例子中,你并没有覆盖任何东西。
所以,实质上:
最后,关于你的评论:当然有一个动物对象。马是延伸动物;所以任何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
的方法。此时,以下是:
eat()
扩展Animal
。Object
的公共(和受保护)方法,发现没有Object
这样的方法。鉴于eat()
编译正常,您可以推断Animal
必须是eat
的私人企业! Animal
没有其他方法可以编译。因此,在不查看Animal
私人业务的情况下,您可以推断Animal
类中的eat()
方法私有!现在让我们假设您的目的是创建另一个名为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)
无法覆盖私有方法。
/* 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()
来自动物