V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
movq
V2EX  ›  程序员

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

  •  
  •   movq · 2022-11-17 13:45:05 +08:00 · 9396 次点击
    这是一个创建于 716 天前的主题,其中的信息可能已经有所发展或是发生改变。
       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 给调用到子类了呢?这样难道不是一种反直觉的结果吗?

    第 1 条附言  ·  2022-11-17 16:43:45 +08:00
    我说的和 C++的比较是这样的:
    ```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;
    }
    ```

    这段代码里面,初始化父类的时候,调用的 print 是父类的,而不是子类的,所以输出结果为

    parent
    child
    child
    第 2 条附言  ·  2022-11-17 19:21:39 +08:00
    不懂为啥有这么多人看不懂题目,比如#10 ,#14 ,#36 ,#41 你题目不看在这说什么呢,建议你们重开另一个帖子发,不要虚空打拳

    为啥有些很多人能看懂,比如#8 ,#23 ,#32 ,#47 等等,你看不懂呢?

    还有些人不知道是不是骨子里自卑,所以觉得我说一个我觉得 Java 里面不如 C++符合我直觉的点,就觉得我在秀 C++优越
    125 条回复    2022-11-21 16:29:04 +08:00
    1  2  
    liuhan907
        1
    liuhan907  
       2022-11-17 13:52:20 +08:00
    如果不是这样的话,那要怎么实现 abstract 方法呢?
    llzzll1234
        2
    llzzll1234  
       2022-11-17 13:54:32 +08:00   ❤️ 7
    因为你实际 new 的是 Child 的对象啊,那当然调用实际对象所拥有的方法实现。
    Parent parent = new Child();这里实际上是向上造型,父类的引用指向了子类的对象,
    子类的初始化先调用父类的构造,所以先调用 Parent#print ,但是因为实际上初始化的对象是 Child ,print 方法由子类 override 了,所以输出的是 20.
    potatowish
        3
    potatowish  
       2022-11-17 13:58:47 +08:00 via iPhone
    调用了父类的构造函数,但是 print 方法被子类重写了啊
    msg7086
        4
    msg7086  
       2022-11-17 13:58:54 +08:00
    否则子类怎么重写父类的方法呢?
    movq
        5
    movq  
    OP
       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
        6
    movq  
    OP
       2022-11-17 14:01:29 +08:00
    觉得 C++的更符合直觉一些。
    msg7086
        7
    msg7086  
       2022-11-17 14:03:05 +08:00   ❤️ 1
    补充一句。在 C++里,父类方法可以选择是否为虚函数,如果是虚函数,那么总是被子类重写,否则才是像你说的调用父类实现。在 Java 里,所有的方法都相当于是虚函数,也就是说所有的方法都会向下查找到子类重写的版本。你只能用 final 去禁止重写,但是做不到你说的这个要求。
    leonshaw
        8
    leonshaw  
       2022-11-17 14:47:37 +08:00   ❤️ 1
    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
        9
    leonshaw  
       2022-11-17 14:49:22 +08:00
    有个问题是这里子类是没初始化的,需要很小心
    wenzhoou
        10
    wenzhoou  
       2022-11-17 14:52:06 +08:00 via Android   ❤️ 25
    举个例子。
    class 人
    method 自我介绍
    说:我叫 xxx

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

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


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

    如果你只是想强制调用父类里的方法,不想去调用子类的,为什么不设置成 final 或者 private 呢?
    fzdwx
        20
    fzdwx  
       2022-11-17 15:48:06 +08:00
    你都`new Child();`了,而且也重写了`print()`方法,这个结果没有任何问题。
    GuuJiang
        21
    GuuJiang  
       2022-11-17 15:59:47 +08:00
    @movq #15 的结论不成立,你用 virtual 方法来测试将会得到和 Java 完全一样的结果,你把 Java 的所有方法都当成 virtual 的就能理解了
    wetalk
        22
    wetalk  
       2022-11-17 16:04:53 +08:00
    不要用你 C++直觉,猜测 Java 语言的特性,每个语言本身特性不尽相同
    cpstar
        23
    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
        24
    cpstar  
       2022-11-17 16:17:35 +08:00
    如果想要知道怎么做到实例 parent 嗲用了 Child.print 的话,可以了解一下 JAVA 虚拟机以及 bytecode

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

    this = child
    itning
        26
    itning  
       2022-11-17 16:24:20 +08:00
    披着羊皮的狼
    kaedeair
        27
    kaedeair  
       2022-11-17 16:24:50 +08:00
    java 并没有 C++这样继承体系,他的继承是基于类型擦除的
    movq
        28
    movq  
    OP
       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
        29
    movq  
    OP
       2022-11-17 16:35:07 +08:00
    @wangxiaoaer 他都没看懂我在问什么,有什么好多看几遍的。
    s1mpleo
        30
    s1mpleo  
       2022-11-17 16:37:05 +08:00 via Android
    我居然没弄懂,这要是面试了可咋办🤯
    zoharSoul
        31
    zoharSoul  
       2022-11-17 16:48:48 +08:00
    10 楼解释的非常清楚了
    hsfzxjy
        32
    hsfzxjy  
       2022-11-17 16:52:22 +08:00 via Android
    所以你想问什么呢?你想问 (1) 为什么 java 不像 c++ 那样 还是 (2) java 怎么才能像 c++ 那样 还是 (3) java 这个机制底层是怎么实现的
    x1aoYao
        33
    x1aoYao  
       2022-11-17 16:53:53 +08:00
    在 c++或者 go 里面确实是按照 op 预期的那样;但是在 java 里不同,所有的类方法都是动态分发的。
    jawe001
        34
    jawe001  
       2022-11-17 16:56:51 +08:00
    其实 Java 中调用实例变量和实例方法时,前面省略了 this 。可以理解为 this.print()。而你 new 的是 Child(),自然这个 this 是指向 Child 这个实例的。因此 this.print() 调用的是 Child 类里面的 print() 实例方法
    gaara
        35
    gaara  
       2022-11-17 16:59:13 +08:00
    是不是搞错了,c++只是在父类的`构造函数`里调用被子类 override 的方法才是调用父类的(因为子类方法这会还没被初始化),其他情况父类指针指向(引用)子类对象调用的还是子类对象 override 的方法
    lisongeee
        36
    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
        37
    listenerri  
       2022-11-17 17:08:52 +08:00
    不同语言的底层实现自然有差异,这也不失为是人家的“特色”,记住就完了呗。就 OP 举的例子表现出的差异来讲,Java 和 C++ 双方选手各执一词,也各有各的优点,我倒没感觉有什么“反直觉”的问题,只是习惯或者惯性思维问题。
    movq
        38
    movq  
    OP
       2022-11-17 17:15:59 +08:00
    @lisongeee 你举的这个例子和我这个帖子完全无关
    movq
        39
    movq  
    OP
       2022-11-17 17:16:36 +08:00
    @hsfzxjy 1 和 3
    yazinnnn
        40
    yazinnnn  
       2022-11-17 17:24:27 +08:00
    你可以尝试用 unsafe 去修改字节码来实现 c++的效果,但是这需要一定的 jvm 功底
    zsdroid
        41
    zsdroid  
       2022-11-17 17:26:09 +08:00
    笑死,op 都没理解多态的意思,就说 10 楼是错的。实际上 10 楼是正解
    liprais
        42
    liprais  
       2022-11-17 17:29:12 +08:00
    我还以为干啥来着,原来是拿 cpp 踩 java 来了
    二十年前就玩烂了好么
    movq
        43
    movq  
    OP
       2022-11-17 17:52:08 +08:00
    @zsdroid 你说的是对的
    movq
        44
    movq  
    OP
       2022-11-17 17:52:19 +08:00
    @liprais 你说的是对的
    pkoukk
        45
    pkoukk  
       2022-11-17 18:10:26 +08:00
    因为里氏替换不是削足适履
    Vtwoguest
        46
    Vtwoguest  
       2022-11-17 18:16:00 +08:00 via iPhone   ❤️ 2
    this.print(),本质上是因为子类对象创建时会默认调用 Super()访问父类构造方法,所以此时父类构造方法里的 this 是子类引用
    geelaw
        47
    geelaw  
       2022-11-17 18:44:11 +08:00   ❤️ 9
    设计不同而已。

    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
        48
    aliveyang  
       2022-11-17 18:50:59 +08:00
    你这样 Parent parent = new Child();其实跟 Child child= new Child();没有任何区别,就是换了张皮
    psycho9631
        49
    psycho9631  
       2022-11-17 18:58:28 +08:00
    对象先创建 再执行构造方法 构造方法的执行 也只是函数的调用而已
    并不是说构造函数执行完毕后 才存在实例(是这样吗 我也不太清楚?)
    所以在父类构造方法调用完成前 子类就已经实例化于内存中了 所以 this.print()就调用实例对象的方法
    有大佬指点下 我的想法吗
    dreamist
        50
    dreamist  
       2022-11-17 18:59:28 +08:00
    这不就是面向对象最核心的一个特性“多态”吗?如果现象打出来不是 20 ,那面对象还搞个啥
    helloworld1024
        51
    helloworld1024  
       2022-11-17 19:06:54 +08:00
    这个问题太 low
    movq
        52
    movq  
    OP
       2022-11-17 19:23:28 +08:00
    @helloworld1024 完全赞同你的观点
    movq
        53
    movq  
    OP
       2022-11-17 19:24:30 +08:00
    @psycho9631 C++在父类构造函数完成之前子类对象还不存在,所以和 Java 不一样
    jiangzm
        54
    jiangzm  
       2022-11-17 19:30:28 +08:00
    没两把刷子就别那啥
    dqzcwxb
        55
    dqzcwxb  
       2022-11-17 19:41:46 +08:00
    梦回 Java 第一课,继承和多态
    movq
        56
    movq  
    OP
       2022-11-17 20:18:38 +08:00 via iPhone
    @jiangzm 听不懂你想表达什么
    movq
        57
    movq  
    OP
       2022-11-17 20:20:20 +08:00 via iPhone
    @jiangzm 哦,你是不是想说我没水平?那你的证据呢?——还是说,凡是你说的都是对的,所以不需要证据?
    movq
        58
    movq  
    OP
       2022-11-17 20:25:54 +08:00 via iPhone
    @jiangzm 关于你说的这句话,“没两把刷子”有可能是对的,因为我确实正在学 Java ,正在学当然水平不高了。但你说的“那啥”想表达什么呢?我发这个帖子是怎么着恶心你了吗?不能问问题是吧?不知道哪来的恶意
    ediron
        59
    ediron  
       2022-11-17 20:26:02 +08:00
    可以把构造方法当作是声明了一段初始化代码,也就是说这里父类的构造方法只是表明了需要执行一下 print() 方法,而子类调用构造方法时必须执行父类的构造方法,这里子类对象执行到 print() 这一行时发现有自己的 print() 方法,当然就执行自己的 print() 方法了。本质上就是多态的特性,构造器由上向下调用,成员由下向上调用。
    movq
        60
    movq  
    OP
       2022-11-17 20:28:56 +08:00 via iPhone
    @ediron 你还是把 Java 多态解释了一遍。但这里问题的核心是构造函数调用虚函数时 Java 和 C++的区别。你可以看 47 楼怎么说的。
    fkdog
        61
    fkdog  
       2022-11-17 20:50:28 +08:00
    你既然都已经覆盖掉父类的方法了,如果父构造器还在调用自身的方法,那你就不怕程序出 bug 么。。
    ShukeShuke
        62
    ShukeShuke  
       2022-11-17 21:04:59 +08:00
    建议看一下 java 的多态性
    ajaxgoldfish
        63
    ajaxgoldfish  
       2022-11-17 21:13:18 +08:00
    封装、继承、多态,去搜搜“Java 使用多态的的好处示例”这个关键词,学到后边就会明白了,spring 中的设计模式很大一部分就是用的这个特性
    AerithLoveMe
        64
    AerithLoveMe  
       2022-11-17 21:13:37 +08:00
    我猜楼主应该是觉得创建了父类对象( new 父类),就应该调用父类自己的方法吧,但子类只是调用了父类的构造方法,是由子类调用的,所以用的是子类的 print 方法
    maninfog
        65
    maninfog  
       2022-11-17 21:28:06 +08:00
    楼主这个贴其实很有意思。如果用 Kotlin 的话,在父类构造函数内掉非 final 方法,IDE 还会给你一个 lint 提示,因为字类如果重写了这个方法,并且在方法内访问了字类自己的成员变量,那么这个方法的调用其实会出问题,因为这个时候字类的成员变量是还没有初始化的。确实就当是特性就好了,习惯了也会避免去这样做。
    ajaxgoldfish
        66
    ajaxgoldfish  
       2022-11-17 22:07:46 +08:00
    @geelaw yes 是这个意思,兄弟你表达能力太强了。
    haya
        67
    haya  
       2022-11-17 22:08:31 +08:00
    父类构造方法里的 this 都指向的是 (你 new 出来的)(未初始化完成的)子类对象
    movq
        68
    movq  
    OP
       2022-11-17 22:08:40 +08:00
    @AerithLoveMe

    我觉得父类不应该调用子类重写的方法,是因为我觉得这就把父类子类搅和在一起,破坏类的封装性了。

    构造函数是给自己初始化的,父类他又不知道别的类写的是什么代码,怎么敢在构造函数里使用别的类的方法来初始化自己?

    不过 Java 确实是这样的特性,因为它初始化父类时子类已经存在了,虚函数表里面的函数就是子类的函数。
    LeegoYih
        69
    LeegoYih  
       2022-11-17 22:39:29 +08:00
    同一个方法,因为调用的对象不同,导致结果有多种,这样才是反直觉的吧?难道不觉得这种方式很恐怖吗?
    littlewing
        70
    littlewing  
       2022-11-17 22:54:29 +08:00
    看了一遍回复,有接近一半的人不审题,或是大概瞄了一眼就开始回复了,连楼主想问什么都没搞清楚
    iseki
        71
    iseki  
       2022-11-17 23:26:33 +08:00
    @movq 其实父类子类本来就搅合在一起了,不差构造函数这一点吧
    mind3x
        72
    mind3x  
       2022-11-17 23:50:13 +08:00   ❤️ 10
    写了 20 多年 Java ,也写过 JVM ,我来尝试解释一下。

    首先 Java 这个行为和 C#是一样的:在子类尚未完成初始化时,父类的构造函数就已经能调用在子类中重载的函数。这意味着不注意的话很容易跑出 NPE 和别的毛病来。这个问题在 stackoverflow 上也经常有人问。

    要理解这个行为,可以看一下 JVM spec 里对 invokevirtual 这个字节码的解释: https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.invokevirtual

    注:invokevirtual 是 JVM bytecode 调用在 class 中定义的 virtual 函数时所使用的指令。

    简单的说,调用虚方法时,查找的是当前实例(this)的类的方法表,也就是你的 Child 的方法表。

    这里 Java 和 C++的区别是,C++的虚函数表(vtable)的建立,在逻辑上是动态的,相当于每一层实例构造完以后,更新一次 vtable 。当然实际上 C++编译器不会这么没效率,就把构造函数里调用的函数当作非虚函数在编译期直接 resolve 完事。

    而 Java 的类的方法表就那么一张,每个类在加载验证 link 完成以后,方法表就在那里不动了。而基类构造函数的调用是在初始化实例时动态发生的,调虚方法时查的表也是 Child 的表,自然会调用到 Child 中重载的函数,即使此时 Child 的数据成员并未初始化。

    这样做在逻辑上确实有难以理解的地方:Child 整个实例都还没处于一个合法的状态,其方法就被调用了。

    但是,C++这种做法也有其局限性:确实有场景是需要基类能在构造函数里调用子类重载的虚函数,只要子类的实现不依赖子类的数据成员即可。打个比方:

    class Bike {
    Bike () {
    frontWheel = makeWheel();
    rearWheel = makeWheel();
    }
    Wheel makeWheel();
    }

    class TitaniumBike {
    Wheel makeWheel() {
    return new TitaniumAllyWheel();
    };
    }

    这样子类就可以正确产生一辆拥有钛合金狗眼(划掉)轮子的自行车。这里不讨论此种设计模式的优劣,只是举个例子。我本人反正是不会这么写。
    geelaw
        73
    geelaw  
       2022-11-18 00:11:40 +08:00 via iPhone   ❤️ 1
    @mind3x #72

    > 当然实际上 C++编译器不会这么没效率,就把构造函数里调用的函数当作非虚函数在编译期直接 resolve 完事。

    很多时候不能这样做,因为构造函数、析构函数可以调用其他成员函数或者把 this 传入其他地方,在其他成员函数里或者通过复制的 this 调用虚函数必须仍然得到正在被构造的类的版本,而且对 this 所指向的对象用 typeid 也必须得到正在被构造的类。安全的做法是反复改变虚函数表指针。
    mind3x
        74
    mind3x  
       2022-11-18 00:18:18 +08:00
    @geelaw 多谢指出,确实不写 C++好多年了。
    qwertyegg
        75
    qwertyegg  
       2022-11-18 04:31:49 +08:00
    爪哇 101:多态性
    mortalbibo
        76
    mortalbibo  
       2022-11-18 07:57:22 +08:00
    java 里称之为动态绑定机制...
    duanguyuan
        77
    duanguyuan  
       2022-11-18 09:12:41 +08:00   ❤️ 1
    10 楼回答清楚明了,op 说这是打虚空打拳?

    退一步讲,先不论别人回答对不对,别人花时间回答你的问题(从语气来看,并未讥讽、引战),你是给钱了还是怎么的,对别人就这么大脾气……
    movq
        78
    movq  
    OP
       2022-11-18 09:21:17 +08:00
    @duanguyuan 我有啥脾气呢?你再看看我问的是什么,然后再看看 10 楼?我说他没看懂我在说什么,是在陈述事实。我跟你这种争论,如果你看懂了题,根本就不该发生。所以我说虚空打拳也是事实——你跟我争论的东西根本就是因为你在和我讨论与本帖毫无关联的内容。
    movq
        79
    movq  
    OP
       2022-11-18 09:22:21 +08:00
    @duanguyuan 真正清楚明了的内容,是本帖里面 mind3x 和 geelaw 的回答,而不是 10 楼这种根本没看懂题还被同样没看懂题的人强行说好的回答。
    movq
        80
    movq  
    OP
       2022-11-18 09:25:05 +08:00
    @duanguyuan 一群看不懂题的人不要在这虚空打拳和我争论浪费大家的时间,浪费版面
    WhiteDragon96
        81
    WhiteDragon96  
       2022-11-18 09:44:42 +08:00
    编译看左,运行看右
    duanguyuan
        82
    duanguyuan  
       2022-11-18 09:49:32 +08:00   ❤️ 1
    这里有几层逻辑不敢苟同。

    ( 1 )你在题目中说 java 这种继承行为反直觉,10 楼举例“哑巴不能说话”,java 的行为反而是符合直觉的。这个例子很直观,所以这么多人给 10 楼送感谢。

    ( 2 )不管哪个论坛社区,有规定看不懂题目的人不能回答问题吗?而且“看不懂题目”这还是个主观论断,或许题目不清楚,或许别人的回答是对的,只是你没 get 到

    ( 3 )关于浪费版面。在大佬看来,开这个贴就是浪费版面。但有人这么说你吗?还真有,说这个问题很 low 。那你心里爽吗?你不爽。所以,别双标。我是菜鸡,我认真回个帖还是浪费版面了?
    llzzll1234
        83
    llzzll1234  
       2022-11-18 09:50:08 +08:00
    别吵了,这个 OP 不知道为什么就一股迷之傲慢,根本不是想和你们讨论问题的样子。
    newmlp
        84
    newmlp  
       2022-11-18 09:52:47 +08:00
    @msg7086 但是我在子类里有时候想调父类的方法,有时候又要调用子类的方法
    movq
        85
    movq  
    OP
       2022-11-18 09:59:09 +08:00
    @duanguyuan


    @duanguyuan

    ( 1 )首先,10 楼说的内容和我的帖子没有任何关系。我是在问为什么构造函数里面调用被重写的函数,会调用到子类,然后我附了一个 C++的代码,说 C++里面,父类构造函数调用被重写的函数,调用到的是父类的方法。

    10 楼回复的,是在说用父类静态类型,子类动态类型,调用静态类型被重写的方法,可以调用到子类里被重写的方法。和我说的完全没有关系。

    不懂这个区别的人建议闭嘴,没必要一直争论。错就是错了,不会因为你们看不懂题目的人一直说就变成对的。

    ( 2 )我没说不能回答问题,我说 10 楼没看懂题。其它没也没看懂题的人不要一直来骚扰楼主,说 10 楼是对的。讨论也是要朝着正确的方向来进行的,而不是不能指出某些人带偏了方向。

    ( 3 )“在大佬看来,开这个贴就是浪费版面”。不要主观臆断,你说的没有根据。为什么楼里面 mind3x 和 geelaw 这两个大佬给出了专业性强,内容丰富的回答,而且没说这个帖子 low ,反而而有些根本没看懂题的人才会说本帖子 low 呢?是不是这些人太自恋了?还是说我指出他们看不懂题,说到他们的痛处了?真是黄钟毁弃,瓦釜雷鸣

    ( 4 )“别吵了,这个 OP 不知道为什么就一股迷之傲慢,根本不是想和你们讨论问题的样子。”

    你说的根本就不属实。我在和能看懂题目的人讨论问题,和他们讨论是平和的讨论的。你所谓的傲慢,是因为有些人没看懂题还来攻击我。对于这些人,我对他们回应的态度是正常的。自己错了就是错了,还逼逼赖赖的攻击对的人。
    movq
        86
    movq  
    OP
       2022-11-18 10:00:52 +08:00
    统一回复:85 楼已经说的很清楚了,看不懂题的人建议不要继续在这回复我,建议去检查一下自己的认知能力。没看懂题就在这 diss 我的人我不会再回复。
    movq
        87
    movq  
    OP
       2022-11-18 10:04:09 +08:00
    @newmlp 子类调用父类已经被子类重写的方法应该可以反射吧
    Maiiiiii
        88
    Maiiiiii  
       2022-11-18 10:10:38 +08:00
    啊对对对
    movq
        89
    movq  
    OP
       2022-11-18 10:17:03 +08:00   ❤️ 1
    有个猜想, 不知道是不是有些人智商不够,看不懂长文,也看不懂专业回答,所以不看本楼里面 mind3x 和 geelaw 这两个大佬发的真正专业的回答,而只能看 10 楼这种写给小学生看的东西,还奉为圭臬。

    从给他们的点赞也能看出来,真正专业的回答只有一两个赞,10 楼这种毫无意义的回答却点赞很多。
    ccppgo
        90
    ccppgo  
       2022-11-18 10:34:12 +08:00
    楼主估计是看到别人回答的东西简单明了, 然后看客都赞同, 他觉得看客们可能会因此觉得他水平很低 所以恼羞成怒了, 有点搞笑的
    JerryV2
        91
    JerryV2  
       2022-11-18 10:35:12 +08:00   ❤️ 1
    @movq

    C++ 的构造顺序其实是很基础的知识了,会 C++ 的人一眼就能看懂问题的所在,你说他们不懂,他们自然认为你傲慢

    #72 #73 是大佬,懂 C++ 的人真是越来越少了
    hez2010
        92
    hez2010  
       2022-11-18 10:36:29 +08:00
    因为 Java 中的方法默认都是 virtual 的,于是 Child 的 print 把 Parent 的 print 重写了;而 C++ 的方法则默认不是 virtual 的。
    duanguyuan
        93
    duanguyuan  
       2022-11-18 10:41:28 +08:00   ❤️ 2
    抱歉,确实智商不够,看了 72 层 mind3x 的回答才明白 op 要问的是什么,10 楼回答确实文不对题。

    打个比方,一个知识点,大家被考了 N 遍了,一遇到类似的题就知道怎么答,惯性思维。但这次的题看起来一样,问的却是另一个更深入的东西,不仔细审题自然答错。

    ps: 楼主说话是真难听。
    movq
        94
    movq  
    OP
       2022-11-18 10:44:08 +08:00
    @duanguyuan 专门难听给那些没看懂题就说难听话 diss 我的人看的
    movq
        95
    movq  
    OP
       2022-11-18 10:45:06 +08:00
    @ccppgo 你的分析一派胡言
    reallittoma
        96
    reallittoma  
       2022-11-18 10:48:22 +08:00   ❤️ 1
    这帖子看下来,我认为 Java 程序员更加傲慢,绝不接受别人指出的半点 [看似] 是在说 Java [有毛病] 的言论。
    movq
        97
    movq  
    OP
       2022-11-18 10:49:59 +08:00
    @reallittoma 而且我也没说 Java 是劣质语言,我只是说在这一点上我觉得不符合我的直觉,结果就有些人逼逼赖赖的说什么我在秀 C++优越
    hez2010
        98
    hez2010  
       2022-11-18 10:59:10 +08:00
    @movq 其实 OOP 语言里也几乎只有 Java 没有写非 virtual 方法的能力。
    像 C++、C# 的方法默认都是非 virtual 的,因此调用时不需要去查找虚表,反而有性能优势,只有需要 virtual 的时候才手动标记上 virtual 允许方法被重写。
    Opportunity
        99
    Opportunity  
       2022-11-18 11:16:16 +08:00
    你说的这个反直觉的结果其实是因为你类比的有问题。

    可以先从 C#/Java 的“析构”来看。C# 里用的是 `~Foo(){}`,但是可以用 `GC.SuppressFinalize(Object)` 方法来阻止这个函数被调用。Java 里这个函数就直接叫“finalize()”了。可见,这些托管语言里是不存在真正的析构函数( Destructor )的,或者说,不允许用户自定义析构的逻辑。替代品是所谓的终结器( finalizer )。

    同样的,这些语言的“构造函数”其实也只是初始化器( inintializer ),虚表这些在 VM 内真正的构造阶段已经完成了,调用这些 inintializer 的时候在 VM 看来你使用的已经是一个完整的对象了。

    要用 cpp 类比的话你也应该保持构造和析构函数是 trival 的,使用 inintializer 和 finalizer 来模拟构造和析构:

    ```cpp
    class Parent {
    public:
    void virtual inintialize() { print(); }

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

    class Child : public Parent {
    public:
    void virtual inintialize() {
    Parent::inintialize();
    print();
    }

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

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

    输出

    child
    child
    child

    和 Java/C# 表现一致
    TArysiyehua
        100
    TArysiyehua  
       2022-11-18 12:12:57 +08:00
    首先构造方法一定会调用父类的,所以 new Child () 的时候就会调用父类的没毛病,因为构造方法没有虚的,所以必须调用,不然没法继承父类的特征。

    其次方法就不是,你不手动调父类的方法就相当于是覆盖了父类的方法,那自然输出的是子类的
    1  2  
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2735 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 36ms · UTC 12:26 · PVG 20:26 · LAX 05:26 · JFK 08:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.