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

下面这段 Java 多态的代码,如何才能改为 Go、 Python 、JS、C++版本?

  •  1
     
  •   jarryli · 1 天前 · 2269 次点击

    面向对象包括封装、继承、组合、多态,其中最不好理解,也最强悍的就是多态。可以说掌握了多态就掌握了面向对象以及设计模式的精髓。

    但是 java 里面的多态实现太过于严苛,以致于其他语言都无法完全模拟。请看下面的代码,用其他语言实现的时候总难以达到类似效果。

    /*
    多态:即同一个行为具有多个不同表现形式或形态的能力。
    表现形式为,子类重写父类方法,实现类实现接口方法,子类重写抽象类方法等。
    多态三个必要条件:继承、重写、父类引用指向子类对象。多态有效消除类型之间的耦合,并提供灵活的可扩展方案。
    本例子简单清晰明了的 Java 多态,能看懂这个例子就懂了什么是多态。
    */
    
    // 父类 A
    class A {
        public String show(D object) {
            return ("A and D");
        }
    
        public String show(A object) {
            return ("A and A");
        }
    
        // 默认注释掉。可开关注释测试下
        // public String show(B object) {
        // return ("A and B");
        // }
    }
    
    // 子类 B
    class B extends A {
        public String show(B object) {
            return ("B and B");
        }
    
        public String show(A object) {
            return ("B and A");
        }
    }
    
    // 孙子类 C
    class C extends B {
    }
    
    // 孙子类 D
    class D extends B {
    }
    
    // 测试验证
    public class PolymorphismSimple {
        public static void main(String[] args) {
            // 父类声明自己
            A a = new A();
            // 父类声明子类
            A ab = new B();
            // 子类声明自己
            B b = new B();
            C c = new C();
            D d = new D();
    
            // 1) A and A 。b 的类型是 B ,也是 B 的实例,A 里没有 show(B)方法,但有 show(A)方法。B 的父类是 A ,因此定位到 A.show(A)。
            System.out.println("1) " + a.show(b));
    
            // 2) A and A 。c 的类型是 C ,也是 C 的实例,C 继承 B ,B 继承 A 。A 里没有 show(C)方法,也没有 show(B)方法,最后指向 A.show(A)。
            System.out.println("2) " + a.show(c));
    
            // 3) A and D, d 的类型是 D ,也是 D 的实例,D 继承 B ,B 继承 A 。A 里有 show(D)方法,直接定位到 A.show(D)。
            System.out.println("3) " + a.show(d));
    
            // 4) B and A, ab 是 B 的实例,但用 A 声明,即向上转型得到的类型是 A ,运行时才能确定具体该调用哪个方法。
            // ab 是 B 的实例对象,但引用类型是 A 。类型是在编译时确定,因此从类型开始定位方法。
            // A 类中没有 show(B)方法,但有 show(A)方法,因为 A 是 B 的父类,ab 也是 A 的实例,于是定位到 A.show(A)方法。
            // 由于 B 是 A 的子类,且 B 重写了 A 的 show(A),A 的方法被覆盖了,于是定位到 B.show(A),这就是动态绑定。
            // 虽然 B 中有 show(B)方法,但是因为 ab 的类型是 A ,编译时根据类型定位到 A 的方法,而不是 B 。
    
            // 以下几种可开关打开/注释代码测试下。
            // -
            // 若 A 里有 show(A)和 show(B),B 里有 show(B)有 show(A),则编译时关联到 A.show(B),因 B 覆盖了 A.show(B),动态绑定到 B.show(B)。
            // -
            // 若 A 里有 show(A)和 show(B),B 里无 show(B)有 show(A),则编译时关联到 A.show(B),因 B 无覆盖,则直接调用 A.show(B)。
            // -
            // 若 A 里有 show(A)无 show(B),B 里无 show(B)有 show(A),则编译时关联到 A.show(A),因 B 覆盖了 A.show(A),动态绑定到 B.show(A)。
            // -
            // 若 A 里有 show(A)无 show(B),B 里无 show(A)有 show(B),则编译时关联到 A.show(A),因 B 无覆盖,则直接调用 A.show(A)。
            // 查找顺序为:编译时根据引用类型确定所属类 -> 根据重载参数类型定位(类型按子->父->祖逐级往上查找)到类的具体方法(包括继承的方法) ->
            // 运行时实例对象覆盖(覆盖只有子->父一层)了引用类型的同名方法 -> 定位到实例对象的方法。
            System.out.println("4) " + ab.show(b));
    
            // 5) B and A 。ab 是 B 的实例,类型是 A 。从 A 类没找到 show(C)方法,也没找到 A.show(B)方法,找到 A.show(A)方法。A.show(A)被 B.show(A)覆盖,因此调用 B.show(A)。
            System.out.println("5) " + ab.show(c));
    
            // 6) A and D 。A 里面有 show(D)的方法,直接定位到。
            System.out.println("6) " + ab.show(d));
    
            // 7) B and B 。B 里面有 show(B)的方法,直接定位到。
            System.out.println("7) " + b.show(b));
    
            // 8) B and B 。B 没有 show(c)方法,但有 show(B)方法。C 继承自 B ,父类型是 B ,因此调用 B.show(B)。
            System.out.println("8) " + b.show(c));
    
            // 9) A and D 。B 中没有 show(D)方法,B 继承 A ,A 里有 show(D), 故调用 A.show(D)方法。
            System.out.println("9) " + b.show(d));
    
            // 10) B and A 。父类声明子类,存在向上转型。A 里有 show(A),被 B.show(A)覆盖了,因此定位到 B.show(A)。
            System.out.println("10) " + ab.show(a));
    
        }
    }
    
    /**
     * 测试结果
     * 1) A and A
     * 2) A and A
     * 3) A and D
     * 4) B and A
     * 5) B and A
     * 6) A and D
     * 7) B and B
     * 8) B and B
     * 9) A and D
     * 10) B and A
     */
    

    其他语言模拟实现: https://github.com/microwind/design-patterns/tree/main/programming-paradigm/object-oriented-programming/polymorphism

    25 条回复    2026-03-19 12:41:47 +08:00
    YanSeven
        1
    YanSeven  
       1 天前
    什么叫类似的效果。
    jarryli
        2
    jarryli  
    OP
       1 天前
    @YanSeven 就是完整模拟 java 多态的效果。
    dcsuibian
        3
    dcsuibian  
       1 天前   ❤️ 3
    不了解 Go 和 C++,但是 JavaScript/TypeScript 和 Python 和 Java 我还是了解的。
    我说直白点,你在其它语言里追求另一种语言的写法是白费工夫

    你这个项目的介绍中提到“研究设计模式”,但是《设计模式》书中说过:
    [img][/img]
    程序设计语言的选择非常重要,它将影响人们理解问题的出发点。我们的设计模式采用了 Smalltalk 和 C++层的语言特性,这个选择实际上决定了哪些机制可以方便地实现,哪些则不能。如果采用过程式语言,那么可能就要包括诸如“继承”“封装”和“多态”的设计模式。相应地,一些特殊的面向对象语言可以直接支持我们的某些模式,例如,CLOS 支持多方法概念,这就减少了访问者等模式的必要性。


    对于你的这个问题,如果你追求的是核心目标:“同一个调用,根据对象的不同产生不同的行为,可以方便地替换实现方式”,那么我可以跟你说 Python 和 JS 是咋做的
    但是你问怎么实现“多态这个效果”,那你追求的不是这个核心目标,而是想复刻 Java 的实现路径

    或者说 Java 味太重了。即使你真的实现了,在 js 和 python 中大家也不那么写,没有用
    pursuer
        4
    pursuer  
       1 天前
    毕竟通用编程语言都是图灵完备的,通常要实现什么逻辑都是可以的,只是有的麻烦有的简单。

    除了 JS 和 Python 这样的脚本语言自带的 eval ,其他语言通常较少具备对等特性。
    liuliuliuliu
        5
    liuliuliuliu  
    PRO
       1 天前
    要不试试 C# ?
    不过第 9 条行为会不同,C# 的方法重载解析规则是“一旦在派生类(B)中找到了适用的方法,就不会再去基类(A)中搜索”。在 C# 中,因为 D 继承自 B ,所以参数 d 适用 B.Show(B)。既然在 B 中找到了,编译器直接忽略基类 A 里的 Show(D)。

    jarryli
        6
    jarryli  
    OP
       1 天前
    @dcsuibian 谢谢。其实我想通过多态来研究语言特性。经验实践 JS Go Py 等均无法实现或者翻译 Java 这种效果。这正是不同语言设计哲学所带来的特性,也很有趣。
    rb6221
        7
    rb6221  
       1 天前
    为什么要模拟
    你举得几个语言都恰好不是 OOP ,或者不完全 OOP 的语言,我都怀疑你是故意立靶子打。
    每个语言有自己的特性和使用场景,用到最合适处就没问题了,非要用在不合适处,那我也没话说
    xuyang2
        8
    xuyang2  
       1 天前
    > 其中最不好理解,也最强悍的就是多态。可以说掌握了多态就掌握了面向对象以及设计模式的精髓。

    裘千尺:“我二十年前就已说过,你公孙家这门功夫难练易破,不练也罢。”
    cheng6563
        9
    cheng6563  
       1 天前
    父类方法要求传入子类的实例这是什么神奇的多态用法...
    daysv
        10
    daysv  
       1 天前
    ai 帮我答了一堆,但是好像不能复制 ai 答案。
    AV1
        11
    AV1  
       1 天前
    有的编程语言没有 overload ,很难模拟出 java 那种多态。
    而且 overload 和 override 同时出现的时候,不同编程语言的处理方式都不一样的。
    xtreme1
        12
    xtreme1  
       1 天前   ❤️ 2
    一个函数可以为不同的类型服务, 就是多态.

    现有的 oop 机制特别是 jvaver 最大的问题在于: 总是把继承和多态混为一谈. 看你的 repo 里面列了这么多语言, 为啥没有体会到 trait 真的是好东西. 但是类似的东西 jvav 这些老古董里面又没有提供, 而是一股脑使用继承, 而继承的耦合又过于严重, 导致发明了各种奇奇怪怪的设计模式来兜底并恶心人.

    主楼的示例代码在类型系统里面属于 coercion/overloading 的 ad hoc polymorphism 和 subtype polymorphism, cpp 实现起来是很轻松的. jvav 的类型表达能力虽然不弱, 但绝对不算强的.
    slowman
        13
    slowman  
       1 天前
    说实话我以为这是 2010 年的帖子
    jiangzm
        14
    jiangzm  
       1 天前
    这种基础问题 AI 能回答的很好 为啥要发帖
    UnluckyNinja
        15
    UnluckyNinja  
       1 天前
    方法重载八股属于糟粕,算是支持方法重载下对于函数协逆变的实现细节。在动态类型语言里有鸭子类型,而且也基本都不支持方法重载,复现 java 的实现细节没什么意义。如果真想了解多态应该去了解类型系统、协变逆变等
    netabare
        16
    netabare  
       1 天前 via iPhone
    什么乱七八糟的…Java 那玩意不就是换个名字的继承。

    要说多态最起码也得 adhoc polymorphism 或者 typeclass 那种吧。就更不用提 row polymorphism 或者 effect polymorphism 了。

    至于你说的这种动态分派,visitor pattern 就能模拟,那只要一个语言能写 visitor pattern ,就能模拟出来。

    Go 、Python 、JS 、C++就不说了,Haskell 都能拿 GADT (不带 typeclass )来模拟。

    哦对了,JS 的 prototype 也可以直接运行时「动态替换」,不知道这是不是你要的效果。
    xgdgsc
        17
    xgdgsc  
       21 小时 28 分钟前 via Android
    hidemyself
        18
    hidemyself  
       19 小时 32 分钟前   ❤️ 1
    难以想象,写出这种 Java 代码的人,希望用这种方式实现什么样的效果
    florentino
        19
    florentino  
       19 小时 27 分钟前
    逻辑尽量简单,不要给自己和他人留心智负担,多写几段代码,累不死人
    ll5270
        20
    ll5270  
       18 小时 58 分钟前
    这就是我不用 java 的原因 逆天写法
    jarryli
        21
    jarryli  
    OP
       18 小时 43 分钟前
    @netabare 您可能还没有调试代码哦,可以试着用其他语言谢谢看,用 AI 也行。
    qrobot
        22
    qrobot  
       17 小时 48 分钟前
    // 1. 构造函数
    function A() {}
    function B() {}
    function C() {}
    function D() {}

    // 2. 原型链继承
    B.prototype = Object.create(A.prototype);
    B.prototype.constructor = B;
    C.prototype = Object.create(B.prototype);
    C.prototype.constructor = C;
    D.prototype = Object.create(B.prototype);
    D.prototype.constructor = D;

    // 3. A 的原型方法
    A.prototype.show = function(obj) {
    if (obj instanceof D) {
    return "A and D";
    } else {
    return "A and A";
    }
    };

    // 4. B 的原型方法(核心修正点)
    B.prototype.show = function(obj) {
    // 模拟 Java 的 A ab = new B() 这种引用类型限制
    if (this._asTypeA) {
    // 在 A 的视角里,只有 show(D) 和 show(A)
    // 传入 b 或 c ,在 A 看来都只能匹配到 show(A)
    if (obj instanceof D) {
    return A.prototype.show.call(this, obj);
    }
    return "B and A"; // B 重写了 A 的 show(A)
    }

    // 模拟 B 自己的视角 (B b = new B())
    // B 拥有 show(B), show(A),并继承了 A 的 show(D)
    if (obj instanceof B && !(obj instanceof D)) {
    // c 也是 B 的一种,所以进入这里
    return "B and B";
    }

    if (obj instanceof A && !(obj instanceof D)) {
    return "B and A";
    }

    // 剩下的交给父类 A 处理(比如处理 D )
    return A.prototype.show.call(this, obj);
    };

    // --- 测试验证 ---
    var a = new A();
    var b = new B();
    var c = new C();
    var d = new D();

    // 模拟 A ab = new B();
    var ab = new B();
    ab._asTypeA = true;

    console.log("1) " + a.show(b)); // A and A
    console.log("2) " + a.show(c)); // A and A
    console.log("3) " + a.show(d)); // A and D
    console.log("4) " + ab.show(b)); // B and A (模拟引用类型为 A)
    console.log("5) " + ab.show(c)); // B and A (模拟引用类型为 A)
    console.log("6) " + ab.show(d)); // A and D
    console.log("7) " + b.show(b)); // B and B
    console.log("8) " + b.show(c)); // B and B
    console.log("9) " + b.show(d)); // A and D
    console.log("10) " + ab.show(a)); // B and A

    秒了, 下一题
    qrobot
        23
    qrobot  
       17 小时 44 分钟前
    @jarryli 所以多态能解决什么问题? 不仅仅不能解决问题,反而增加新增负担。 做 Java 第一堂课就是 Favor composition over inheritance , 还在这里整 extends 就说明 Java 没学好, 或者 阿里味太浓
    qrobot
        24
    qrobot  
       17 小时 39 分钟前
    Extensibility 和 Decoupling 都把项目搞复杂化了, 大部分接口几乎永远不会变化的。 就算变化的接口几乎就是重写。 甚至还要改参数。 所以 Extensibility 和 Decoupling 的意义在于什么? 过度设计的产物。软件开发不可能通过 Extensibility 和 Decoupling 来提升所谓的可维护/可替换/提高灵活性. 实际上这些都是需要人来进行维护的。 软件设计不可能一成不变,而是在重构, 在重构. 在复用在重构。
    jarryli
        25
    jarryli  
    OP
       16 小时 54 分钟前
    @qrobot 👍。这里多态示例里展示的确实很复杂,这只是为了说明 Java 语言多态的原理,并非宣扬多态。其他语言特性不同,是没有必要那么实现的,就好像一个 JS 里 bind 就可以改变上下文的调用者,没必要 java 那么复杂的转型。你的 [/ 4. B 的原型方法(核心修正点)] 实现看起来更复杂,而且显式地把 A B C D 都写进代码里去了。
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   946 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 21:36 · PVG 05:36 · LAX 14:36 · JFK 17:36
    ♥ Do have faith in what you're doing.