令人困惑的 Java 输出与继承和覆盖方法 [英] Perplexing output in Java with inheritance and overriding methods

查看:24
本文介绍了令人困惑的 Java 输出与继承和覆盖方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我偶然发现了这段代码.
在实际运行之前,我试图猜测运行它的结果是什么.当我看到他们时,我真的很困惑 &需要一些解释.
这是代码:

I stumbled upon this piece of code.
I tried to guess what will be the result of running it before actually doing so. I was really confused when I saw them & in need of some explanations.
This is the code:

public class A {
    String bar = "A.bar";
    A() { foo(); }

    public void foo() {
        System.out.println("A.foo(): bar = " + bar);
    }
}

public class B extends A {
    String bar = "B.bar";
    B() { foo(); }
    public void foo() {
        System.out.println("B.foo(): bar = " + bar);
    }
}

public class C {
    public static void main(String[] args) {
        A a = new B();
        System.out.println("a.bar = " + a.bar);
        a.foo();
    }
}

输出为:

B.foo(): bar = null
B.foo(): bar = B.bar
a.bar = A.bar
B.foo(): bar = B.bar

这是为什么?

  • bar = null 怎么样?
  • 为什么会出现a.bar = A.bar?我根本没有实例化 A.
  • 如果A出现了,为什么是after B?
  • How is bar = null?
  • Why is a.bar = A.bar even appear? I haven't instantiated A at all.
  • And if A appears, why is it after B?

推荐答案

在我开始解释代码执行中的每一步之前,您应该了解一些事实:

There are a few facts you ought to know before I start explaining every single step in your code's execution:

  • 字段引用根据引用类型解析,方法调用在运行时(以动态方式)根据对象类型解析.
  • super()隐式放置在 every 构造函数中,即使你自己没有把它放在那里(它不会被调用,如果你例如调用 super(int x, int y)).
  • 从构造函数调用可覆盖"方法被认为是非常糟糕的做法 - 当我们执行完后,您就会明白原因.
  • Field references are resolved based on reference type and method calls are resolved during run-time (in a dynamic-fashion) based on the object type.
  • super() is placed implicitly in every constructor even if you don't put it there yourself (it is not called if you call super(int x, int y) for instance).
  • It is considered very bad practice to call "override-able" methods from a constructor - you will see why when we go through the execution.

现在让我们逐步分解您的代码:

Now let's break down your code step-by-step:

  • 您可以通过调用其默认构造函数 B() 来实例化 B.
  • 正如我之前所说,对 super() 的调用被隐式添加到任何构造函数中,因此 A() 会立即被调用.
  • A() 内部调用 foo(),它在 B 类中被覆盖,这就是为什么 foo() 是从 B 调用的.
  • Bfoo() 里面你得到输出 B.foo(): bar = null 因为 Java 没有得到尚未初始化B的字段(其构造函数尚未执行!),对象类型的字段默认初始化为null.
  • 现在我们已经完成了 A() 的构造函数,我们回到 B() 的构造函数.
  • 在上述构造函数中,我们再次调用了foo(),也就是Bfoo().但与上次不同的是,B 正确初始化了它的字段(在调用 super() 之后),所以你得到了预期的 B.foo(): bar= B.bar.
  • 现在我们又回到了 main 的温暖怀抱中.
  • 你访问a.bar,正如我所说的字段引用是基于引用类型解析的,你得到Abar>.
  • 最后,出于同样的原因,您调用 a.foo() 再次触发 Bfoo() 打印 b.bar 再次.
  • You instantiate B by calling its default constructor B().
  • As I said before, the call to super() is added implicitly to any constructor, so A() is immediately called.
  • Inside A() you call foo(), which is overridden in class B and that is why foo() is called from B.
  • Inside B's foo() you get the output B.foo(): bar = null since Java didn't get to initialize B's fields yet (its constructor hasn't been executed yet!) and the fields of object type are initialized to null by default.
  • Now that we are done with the constructor of A() we go back to the constructor of B().
  • Inside said constructor, we have the call to foo() again, which is again B's foo(). But different from last time, B have its fields initialized (after the call to super()) properly so you get the expected B.foo(): bar = B.bar.
  • Now we are back to the warm embrace of main.
  • You access a.bar, and since as I said field references are resolved based on reference type, you get the field bar of A.
  • Lastly, for the same reason, you call a.foo() which again triggers B's foo() which prints b.bar once again.

我们完成了!:)

进一步的参考资料和有价值的阅读材料:
静态和动态绑定解释
构造函数调用顺序

Further references and worthwhile reading materials:
Static and dynamic binding explained
Order of constructor calls

这篇关于令人困惑的 Java 输出与继承和覆盖方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆