为什么 Java 父类构造函数调用被重写的方法会调用到子类的

2022-11-17 13:45:05 +08:00
 movq
   public static void main(String[] args) {
        class Parent {

            Parent() {
                print();
            }

            void print() {
                System.out.println(10);
            }
        }

        class Child extends Parent {


            void print() {
                System.out.println(20);
            }
        }
        Parent parent = new Child();
    }

上面这段代码输出是 20.

感觉这样很奇怪,为什么父类的构造函数调用 print 给调用到子类了呢?这样难道不是一种反直觉的结果吗?

9667 次点击
所在节点    程序员
125 条回复
zsdroid
2022-11-17 17:26:09 +08:00
笑死,op 都没理解多态的意思,就说 10 楼是错的。实际上 10 楼是正解
liprais
2022-11-17 17:29:12 +08:00
我还以为干啥来着,原来是拿 cpp 踩 java 来了
二十年前就玩烂了好么
movq
2022-11-17 17:52:08 +08:00
@zsdroid 你说的是对的
movq
2022-11-17 17:52:19 +08:00
@liprais 你说的是对的
pkoukk
2022-11-17 18:10:26 +08:00
因为里氏替换不是削足适履
Vtwoguest
2022-11-17 18:16:00 +08:00
this.print(),本质上是因为子类对象创建时会默认调用 Super()访问父类构造方法,所以此时父类构造方法里的 this 是子类引用
geelaw
2022-11-17 18:44:11 +08:00
设计不同而已。

Java:对象在任何时刻的类型都是最终类,无论何时,调用虚方法的效果都是最终重写。

C++:对象在构造、析构期间的类型是当前类,可能不是最终类,此时直接或者间接调用虚函数会得到当前类的重写。此时也不可通过 this 访问兄弟类,即使整个对象的兄弟类子对象的构造函数已经运行。

C++ 的设计理念基于非祖先类子对象状态的不确定性。

你可以认为 C++ 里面祖先类的构造函数在子类构造函数之外,而 Java 里面祖先类的构造函数在子类的构造函数之内,且处于开头。

也就是说,考虑 A : B 以及 B::B() { B_ctor_body; } A::A() { A_ctor_body; } 以及 new A(),则

Java 认为发生的事情是

{
/* vtable = A_vtable */
{ B_ctor_body; } // B::B
A_ctor_body;
} // A::A

而 C++ 认为发生的事情是

{ /* vtable = B_vtable */ B_ctor_body; } // B::B
{ /* vtable = A_vtable */ A_ctor_body; } // A::A
aliveyang
2022-11-17 18:50:59 +08:00
你这样 Parent parent = new Child();其实跟 Child child= new Child();没有任何区别,就是换了张皮
psycho9631
2022-11-17 18:58:28 +08:00
对象先创建 再执行构造方法 构造方法的执行 也只是函数的调用而已
并不是说构造函数执行完毕后 才存在实例(是这样吗 我也不太清楚?)
所以在父类构造方法调用完成前 子类就已经实例化于内存中了 所以 this.print()就调用实例对象的方法
有大佬指点下 我的想法吗
dreamist
2022-11-17 18:59:28 +08:00
这不就是面向对象最核心的一个特性“多态”吗?如果现象打出来不是 20 ,那面对象还搞个啥
helloworld1024
2022-11-17 19:06:54 +08:00
这个问题太 low
movq
2022-11-17 19:23:28 +08:00
@helloworld1024 完全赞同你的观点
movq
2022-11-17 19:24:30 +08:00
@psycho9631 C++在父类构造函数完成之前子类对象还不存在,所以和 Java 不一样
jiangzm
2022-11-17 19:30:28 +08:00
没两把刷子就别那啥
dqzcwxb
2022-11-17 19:41:46 +08:00
梦回 Java 第一课,继承和多态
movq
2022-11-17 20:18:38 +08:00
@jiangzm 听不懂你想表达什么
movq
2022-11-17 20:20:20 +08:00
@jiangzm 哦,你是不是想说我没水平?那你的证据呢?——还是说,凡是你说的都是对的,所以不需要证据?
movq
2022-11-17 20:25:54 +08:00
@jiangzm 关于你说的这句话,“没两把刷子”有可能是对的,因为我确实正在学 Java ,正在学当然水平不高了。但你说的“那啥”想表达什么呢?我发这个帖子是怎么着恶心你了吗?不能问问题是吧?不知道哪来的恶意
ediron
2022-11-17 20:26:02 +08:00
可以把构造方法当作是声明了一段初始化代码,也就是说这里父类的构造方法只是表明了需要执行一下 print() 方法,而子类调用构造方法时必须执行父类的构造方法,这里子类对象执行到 print() 这一行时发现有自己的 print() 方法,当然就执行自己的 print() 方法了。本质上就是多态的特性,构造器由上向下调用,成员由下向上调用。
movq
2022-11-17 20:28:56 +08:00
@ediron 你还是把 Java 多态解释了一遍。但这里问题的核心是构造函数调用虚函数时 Java 和 C++的区别。你可以看 47 楼怎么说的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://tanronggui.xyz/t/895919

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX