即使对象属于子类,也会调用超类方法
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
这是我所知道的:
- 因为我们有
private void eat()
方法,它不一定会在子class中访问,所以这里不会出现方法覆盖的问题,因为JLS定义了它清楚。
- 现在这不是方法覆盖,它肯定不会从 Horse class
中调用 public void eat()
方法
- 现在我们的声明
Animal a = new Horse();
由于多态性而有效。
为什么 a.eat()
从 Animal
class 调用方法?我们正在创建一个 Horse
对象,那么为什么要调用 Animal class' 方法?
标记为 private
的方法无法在子class 中覆盖,因为它们对子class 不可见。从某种意义上说,您的 Horse
class 根本不知道 Animal
有一个 eat
方法,因为它被标记为 private
。因此,Java 不会将 Horse
的 eat
方法视为重写。这主要是作为一项安全功能而设计的。如果 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()
)。在大多数情况下,这就是后期绑定的神奇之处。
对于无法重写的实例方法,编译器会执行与静态方法类似的操作,并生成直接调用该方法的指令。
(以上内容在技术上并不准确,只是一个概念性的说明,供您了解发生了什么)
无法重写私有方法。
/* 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() 上,您也会收到编译器错误。因为,正如其他人很好地解释的那样:您没有覆盖示例中的任何内容。
所以,本质上:
- 你没有覆盖任何东西
- 您没有调用您认为正在调用的方法
最后,关于您的评论:当然有一个 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
的私事。
接下来,您查看 Animal
的 public static void main
方法并正确推断那里定义了一个名为 eat()
的方法。此时,以下内容成立:
- 与 Java 中的任何其他 class 一样,
Animal
扩展了 Object
。
- 你查看
Object
的public(和protected)方法,发现没有eat()
这样的方法。鉴于 Animal
编译正常,您可以推断 eat
ing 一定是 Animal
的私事! Animal
没有其他方法可以编译。因此,不看Animal
的私有业务,你可以推断Animal
class中有一个eat()
方法,即private[=73] =]!
现在假设您的意图是创造另一种名为 Horse
的动物作为专门的 Animal
并赋予它特殊的进食行为。您认为您 不会 去研究 Java Lang Spec 并找出这样做的所有规则,然后只需使用 extends
关键字即可完成它。然后出现了 Horse
的第一个版本。然而,您在某处听说,最好阐明您 overriding 的意图(这是您现在确定的一件事——您确实想要 override 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 中的覆盖处理对我们来说变得更加清晰。由于我们应该只覆盖为覆盖而设计的行为,即 public 和受保护的方法,因此我们应该注意 superclasses 的编写方式。在这种情况下,无意中,显然是为扩展而设计的 superclass Animal
使 subclass 无法覆盖 eat
ing 行为!
即使我们删除了 @Override
标记以摆脱编译器的技术性 error/warning,我们也不一定会做正确的事情,因为正如您所观察到的,在运行时,签名匹配的意外方法被调用。那更糟。
您写道:
Animal a = new Horse();
在这种情况下 a 指向 Horse 对象就好像它是 Animal[=24= 的对象一样] 类型。
a.eat()
在 Animal class 中是私有的,所以它不能被覆盖,这就是为什么 a.eat()
来自 动物
我在尝试简单的重载覆盖规则并发现了一些有趣的东西。这是我的代码。
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
这是我所知道的:
- 因为我们有
private void eat()
方法,它不一定会在子class中访问,所以这里不会出现方法覆盖的问题,因为JLS定义了它清楚。 - 现在这不是方法覆盖,它肯定不会从 Horse class 中调用
- 现在我们的声明
Animal a = new Horse();
由于多态性而有效。
public void eat()
方法
为什么 a.eat()
从 Animal
class 调用方法?我们正在创建一个 Horse
对象,那么为什么要调用 Animal class' 方法?
标记为 private
的方法无法在子class 中覆盖,因为它们对子class 不可见。从某种意义上说,您的 Horse
class 根本不知道 Animal
有一个 eat
方法,因为它被标记为 private
。因此,Java 不会将 Horse
的 eat
方法视为重写。这主要是作为一项安全功能而设计的。如果 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()
)。在大多数情况下,这就是后期绑定的神奇之处。
对于无法重写的实例方法,编译器会执行与静态方法类似的操作,并生成直接调用该方法的指令。
(以上内容在技术上并不准确,只是一个概念性的说明,供您了解发生了什么)
无法重写私有方法。
/* 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() 上,您也会收到编译器错误。因为,正如其他人很好地解释的那样:您没有覆盖示例中的任何内容。
所以,本质上:
- 你没有覆盖任何东西
- 您没有调用您认为正在调用的方法
最后,关于您的评论:当然有一个 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
的私事。
接下来,您查看 Animal
的 public static void main
方法并正确推断那里定义了一个名为 eat()
的方法。此时,以下内容成立:
- 与 Java 中的任何其他 class 一样,
Animal
扩展了Object
。 - 你查看
Object
的public(和protected)方法,发现没有eat()
这样的方法。鉴于Animal
编译正常,您可以推断eat
ing 一定是Animal
的私事!Animal
没有其他方法可以编译。因此,不看Animal
的私有业务,你可以推断Animal
class中有一个eat()
方法,即private[=73] =]!
现在假设您的意图是创造另一种名为 Horse
的动物作为专门的 Animal
并赋予它特殊的进食行为。您认为您 不会 去研究 Java Lang Spec 并找出这样做的所有规则,然后只需使用 extends
关键字即可完成它。然后出现了 Horse
的第一个版本。然而,您在某处听说,最好阐明您 overriding 的意图(这是您现在确定的一件事——您确实想要 override 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 中的覆盖处理对我们来说变得更加清晰。由于我们应该只覆盖为覆盖而设计的行为,即 public 和受保护的方法,因此我们应该注意 superclasses 的编写方式。在这种情况下,无意中,显然是为扩展而设计的 superclass Animal
使 subclass 无法覆盖 eat
ing 行为!
即使我们删除了 @Override
标记以摆脱编译器的技术性 error/warning,我们也不一定会做正确的事情,因为正如您所观察到的,在运行时,签名匹配的意外方法被调用。那更糟。
您写道:
Animal a = new Horse();
在这种情况下 a 指向 Horse 对象就好像它是 Animal[=24= 的对象一样] 类型。
a.eat()
在 Animal class 中是私有的,所以它不能被覆盖,这就是为什么 a.eat()
来自 动物