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

8950 次点击
所在节点    程序员
125 条回复
GuuJiang
2022-11-17 15:59:47 +08:00
@movq #15 的结论不成立,你用 virtual 方法来测试将会得到和 Java 完全一样的结果,你把 Java 的所有方法都当成 virtual 的就能理解了
wetalk
2022-11-17 16:04:53 +08:00
不要用你 C++直觉,猜测 Java 语言的特性,每个语言本身特性不尽相同
cpstar
2022-11-17 16:10:11 +08:00
我好像提过类似的问题。我当时的问题是想办法外部调用的时候,调用 Parent 的而不是 Child 的。
但这确实就是 JAVA 的继承机制,重写 overwrite 父类方法。从 bytecode 看,Child 的实例,也就是 parent 相应的 method 入口在 Child.print 上,除非反射,否则类加载器永远无法找到 Parent.print 。

另外这里还有修饰符,print 的没有显式写,所以默认是 public ,那 Child 就 overwrite 了 print ,如果修饰符为 private ,那就不会 overwrite ,本级调用本级,次级调用次级,不会互相串。
cpstar
2022-11-17 16:17:35 +08:00
如果想要知道怎么做到实例 parent 嗲用了 Child.print 的话,可以了解一下 JAVA 虚拟机以及 bytecode

如果想要知道为什么要这么做的话,那就得问 Bjarne Stroustrup 和 James Gosling 了。🤣
wolfie
2022-11-17 16:17:55 +08:00
this.print()

this = child
itning
2022-11-17 16:24:20 +08:00
披着羊皮的狼
kaedeair
2022-11-17 16:24:50 +08:00
java 并没有 C++这样继承体系,他的继承是基于类型擦除的
movq
2022-11-17 16:33:44 +08:00
@GuuJiang 我说的是哪一步不成立呢?

```cpp
class Parent {

public:
Parent() {
print();
}

void virtual print() {
std::cout << "parent" << std::endl;
}
};

class Child : public Parent {

public:
Child() {
print();
}

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

int main() {
Parent *p = new Child();
p->print();
return 0;
}
```

执行结果

parent
child
child
movq
2022-11-17 16:35:07 +08:00
@wangxiaoaer 他都没看懂我在问什么,有什么好多看几遍的。
s1mpleo
2022-11-17 16:37:05 +08:00
我居然没弄懂,这要是面试了可咋办🤯
zoharSoul
2022-11-17 16:48:48 +08:00
10 楼解释的非常清楚了
hsfzxjy
2022-11-17 16:52:22 +08:00
所以你想问什么呢?你想问 (1) 为什么 java 不像 c++ 那样 还是 (2) java 怎么才能像 c++ 那样 还是 (3) java 这个机制底层是怎么实现的
x1aoYao
2022-11-17 16:53:53 +08:00
在 c++或者 go 里面确实是按照 op 预期的那样;但是在 java 里不同,所有的类方法都是动态分发的。
jawe001
2022-11-17 16:56:51 +08:00
其实 Java 中调用实例变量和实例方法时,前面省略了 this 。可以理解为 this.print()。而你 new 的是 Child(),自然这个 this 是指向 Child 这个实例的。因此 this.print() 调用的是 Child 类里面的 print() 实例方法
gaara
2022-11-17 16:59:13 +08:00
是不是搞错了,c++只是在父类的`构造函数`里调用被子类 override 的方法才是调用父类的(因为子类方法这会还没被初始化),其他情况父类指针指向(引用)子类对象调用的还是子类对象 override 的方法
lisongeee
2022-11-17 17:05:48 +08:00
如果按 op 的设想, Java 继承岂不是废了一半?

```java
public class MyClass {
public static class Child {
public String toString() {
System.out.println(10);
return "Child";
}
}
public static void main(String args[]) {
Object obj = new Child();
System.out.println(obj.toString());
}
}
```

![image]( https://user-images.githubusercontent.com/38517192/202402992-fe1beb78-cbc4-4f84-a5c0-accf37c2d3d0.png)
listenerri
2022-11-17 17:08:52 +08:00
不同语言的底层实现自然有差异,这也不失为是人家的“特色”,记住就完了呗。就 OP 举的例子表现出的差异来讲,Java 和 C++ 双方选手各执一词,也各有各的优点,我倒没感觉有什么“反直觉”的问题,只是习惯或者惯性思维问题。
movq
2022-11-17 17:15:59 +08:00
@lisongeee 你举的这个例子和我这个帖子完全无关
movq
2022-11-17 17:16:36 +08:00
@hsfzxjy 1 和 3
yazinnnn
2022-11-17 17:24:27 +08:00
你可以尝试用 unsafe 去修改字节码来实现 c++的效果,但是这需要一定的 jvm 功底

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

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

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

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

© 2021 V2EX