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

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

  •  
  •   jarryli · 1 小时 57 分钟前 · 338 次点击

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

    但是 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

    7 条回复    2026-03-18 20:47:16 +08:00
    YanSeven
        1
    YanSeven  
       1 小时 45 分钟前
    什么叫类似的效果。
    jarryli
        2
    jarryli  
    OP
       1 小时 27 分钟前
    @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 小时 3 分钟前
    毕竟通用编程语言都是图灵完备的,通常要实现什么逻辑都是可以的,只是有的麻烦有的简单。

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

    jarryli
        6
    jarryli  
    OP
       46 分钟前
    @dcsuibian 谢谢。其实我想通过多态来研究语言特性。经验实践 JS Go Py 等均无法实现或者翻译 Java 这种效果。这正是不同语言设计哲学所带来的特性,也很有趣。
    rb6221
        7
    rb6221  
       18 分钟前
    为什么要模拟
    你举得几个语言都恰好不是 OOP ,或者不完全 OOP 的语言,我都怀疑你是故意立靶子打。
    每个语言有自己的特性和使用场景,用到最合适处就没问题了,非要用在不合适处,那我也没话说
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   3243 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 13:05 · PVG 21:05 · LAX 06:05 · JFK 09:05
    ♥ Do have faith in what you're doing.