为了账号安全,请及时绑定邮箱和手机立即绑定

Java多态 - 如何确定是否会调用超类与子类方法以及超类变量与子类变量?

Java多态 - 如何确定是否会调用超类与子类方法以及超类变量与子类变量?

catspeake 2021-11-17 16:59:40
示例代码:public class A {    public int number;    public A(int number) {        this.number = number;    }    public int getNumber() {        return number;    }}public class B extends A{    public int number;    public B(int number) {        super(number);    }    public int getNumber() {        return number;    }}public class C {    public static void main(String args[]) {        A test1 = new B(2);        B test2 = new B(2);        System.out.println(test1.number) // prints 2        System.out.println(test2.number) // prints 0        System.out.println(test1.getNumber()) //prints 0        System.out.println(test2.getNumber()) // prints 0    }}如上所示,test1.number 不等于 test1.getNumber()。因此,当我使 test1 成为 A 类型的对象时, test1.number 指的是 A 类中的 int 数。但是当我调用 test1.getNumber() 时,它是在 B 类中调用 getNumber() 吗?为什么会这样?
查看完整描述

3 回答

?
慕侠2389804

TA贡献1719条经验 获得超6个赞

Java 中的所有方法都是虚拟的。我马上就会明白这意味着什么。

所以,在这一行:

A test1 = new B(2);

您已将类的新实例分配给B声明为包含A.

当您使用 时test1.number,您使用的是number在 class 中声明的变量A,假设B调用的构造函数super(number)将为 2。

但是,当您调用 时test1.getNumber(),这就是 virtual 发挥作用的地方。Virtual 意味着它将始终调用构造类中的方法,而不是您将其声明为的变量类型。换句话说,不是像你想象AgetNumber那样调用's ,它实际上调用的是B's getNumber。 B的构造函数没有为Bnumber变量赋值,所以你得到 0。


查看完整回答
反对 回复 2021-11-17
?
长风秋雁

TA贡献1757条经验 获得超7个赞

我根据这个网站找到了一些解释 Java 类层次结构的信息。当您扩展一个类时,您基本上是从该类复制代码并将其放入扩展它的代码中,这样您就不必重写方法,而是在类 B 中进行。因此,不要在类中使用该方法A 类它将使用您编码到 B 类中的那个。

https://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html


查看完整回答
反对 回复 2021-11-17
?
慕斯王

TA贡献1864条经验 获得超2个赞

那是因为 Java 允许覆盖方法,但不允许覆盖字段。

规范写道

如果一个类声明了一个具有特定名称的字段,那么该字段的声明被称为隐藏了超类和类的超接口中具有相同名称的字段的任何和所有可访问声明。

在这方面,字段隐藏不同于方法隐藏(第 8.4.8.3 节),因为在字段隐藏中没有区分静态和非静态字段,而在方法隐藏中区分静态和非静态方法.

如果隐藏字段是静态的,则可以使用限定名称(第 6.5.6.2 节)访问隐藏字段,或者使用包含关键字 super(第 15.11.2 节)或转换为超类类型的字段访问表达式。

在这方面,字段的隐藏类似于方法的隐藏。

也就是说,每个实例B都有两个不同的字段,它们碰巧共享相同的名称。当您在诸如 的字段访问表达式中使用字段名称时test1.number,将根据 的编译时类型解释该字段名称test1

相反,实例方法可以被覆盖

在类 C 中声明(或由其继承)的实例方法 mC 从 C 中覆盖在类 A 中声明的另一个方法 mA,如果以下所有条件都为真:

  • C 是 A 的子类。

  • ...

  • mC 的签名是 mA 签名的子签名(第 8.4.2 节)。

(其中“子签名”表示方法名称和参数兼容。)

覆盖解决如下

否则,将调用实例方法并存在目标引用。如果目标引用为空,则此时抛出 NullPointerException。否则,目标引用被称为指向目标对象,并将用作调用方法中关键字 this 的值。

...

否则,可能会发生 [...] 覆盖。使用动态方法查找。动态查找过程从一个类 S 开始,确定如下:

  • 如果调用模式是接口或虚拟,则 S 最初是目标对象的实际运行时类 R。

  • ...

动态方法查找使用以下过程来搜索类 S,然后根据需要搜索类 S 的超类和超接口以查找方法 m。

也就是说,如果您编写test1.getNumber(),运行时将评估test1以获取目标对象,确定其类,然后在该类中寻找合适的方法。

在您的示例中,test1引用了 class 的对象B,这B.getNumber()就是调用的原因。

B.getNumber()then的实现继续 read B.number,它从未被分配过,因此仍然包含它的默认值 0。


查看完整回答
反对 回复 2021-11-17
  • 3 回答
  • 0 关注
  • 184 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信