为什么 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 给调用到子类了呢?这样难道不是一种反直觉的结果吗?

8907 次点击
所在节点    程序员
125 条回复
liuhan907
2022-11-17 13:52:20 +08:00
如果不是这样的话,那要怎么实现 abstract 方法呢?
llzzll1234
2022-11-17 13:54:32 +08:00
因为你实际 new 的是 Child 的对象啊,那当然调用实际对象所拥有的方法实现。
Parent parent = new Child();这里实际上是向上造型,父类的引用指向了子类的对象,
子类的初始化先调用父类的构造,所以先调用 Parent#print ,但是因为实际上初始化的对象是 Child ,print 方法由子类 override 了,所以输出的是 20.
potatowish
2022-11-17 13:58:47 +08:00
调用了父类的构造函数,但是 print 方法被子类重写了啊
msg7086
2022-11-17 13:58:54 +08:00
否则子类怎么重写父类的方法呢?
movq
2022-11-17 14:00:53 +08:00
C++的继承是这样的:

```c++
class Parent {

public:
Parent() {
print();
}

void print() {
std::cout << 10 << std::endl;
}
};

class Child : public Parent {

public:
Child() {
print();
}

void print() {
std::cout << 20 << std::endl;
}
};

int main() {
Parent *p = new Child();
return 0;
}
```
输出结果为
10
20

也就是说父类就调用父类的 print ,子类就调用子类的 print
movq
2022-11-17 14:01:29 +08:00
觉得 C++的更符合直觉一些。
msg7086
2022-11-17 14:03:05 +08:00
补充一句。在 C++里,父类方法可以选择是否为虚函数,如果是虚函数,那么总是被子类重写,否则才是像你说的调用父类实现。在 Java 里,所有的方法都相当于是虚函数,也就是说所有的方法都会向下查找到子类重写的版本。你只能用 final 去禁止重写,但是做不到你说的这个要求。
leonshaw
2022-11-17 14:47:37 +08:00
C++:
When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class’s non-static data members, and the object to
which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor’s or destructor’s class and not one overriding it in a more-derived class.

我觉得这里 Java 更符合直觉
leonshaw
2022-11-17 14:49:22 +08:00
有个问题是这里子类是没初始化的,需要很小心
wenzhoou
2022-11-17 14:52:06 +08:00
举个例子。
class 人
method 自我介绍
说:我叫 xxx

class 哑巴 extends 人
method 自我介绍
说:阿巴阿巴

人 张三 = new 哑巴()
张三.自我介绍()


这样结果是什么自然而然就知道了吧。
Jooooooooo
2022-11-17 14:55:07 +08:00
new Child() 等于这是一个 Child, 调用的方法也自然是 Child 里的.
byte10
2022-11-17 15:06:10 +08:00
其实省略了一个 this ,代码中 print() 等同 this.print() 写法。 这里 this 代表实际当前对象引用,也就是子类 Child ,这样解释是否能更好理解点。
movq
2022-11-17 15:09:11 +08:00
@leonshaw 父类调用一个他认为自己已经有的方法,结果调用到子类去了。假设父类的这个方法里面的逻辑和父类本身的内容有关,这个方法将不会达到预期效果。所以 C++的方式更好。这里面没有说抽象方法。如果是抽象方法那都应该是调用 overrider
whileFalse
2022-11-17 15:15:51 +08:00
@movq 那请问子类覆盖父类方法的时候是干什么吃的?
movq
2022-11-17 15:22:56 +08:00
@whileFalse C++里面用父类指针调用方法调用的结果是子类的方法,但是在父类内部调用被重写的方法,调用的仍然是父类自身的方法
leonshaw
2022-11-17 15:26:29 +08:00
@movq Java 的方式让方法在构造函数和平时保持一致,如果父类不希望 override ,可以声明为 final 或者 private ?
C++ 的逻辑是在子类构造开始之前,它还不是一个子类对象,这时子类的函数不可用。个人认为 C++ 更严谨,Java 更直觉。
coderge
2022-11-17 15:33:30 +08:00
https://www.bilibili.com/video/BV1fh411y7R8?p=310&vd_source=9ca206602067ee299613c675960feb79 等号左边是编译类型, 限制了可以调用哪些函数; 等号右边是运行类型, 运行 parent.print 时是调用的子类的 print 函数.
wangxiaoaer
2022-11-17 15:36:18 +08:00
10 楼答案简单明了,建议楼主多看几遍。
msg7086
2022-11-17 15:45:07 +08:00
顺便一提,你说的直觉并不一定是直觉,可能只是一个 C++用户的直觉。
非 C++用户和 C++用户会有不同的直觉。
你用 C++所以你觉得和 C++行为相同的行为就是直觉的,不同的行为就是反直觉的。

如果你只是想强制调用父类里的方法,不想去调用子类的,为什么不设置成 final 或者 private 呢?
fzdwx
2022-11-17 15:48:06 +08:00
你都`new Child();`了,而且也重写了`print()`方法,这个结果没有任何问题。

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

https://www.v2ex.com/t/895919

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

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

© 2021 V2EX