咨询一个继承、重载、父类、多态的方法调用问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
cpstar
V2EX    Java

咨询一个继承、重载、父类、多态的方法调用问题

  •  
  •   cpstar 2022 年 1 月 30 日 4635 次点击
    这是一个创建于 1545 天前的主题,其中的信息可能已经有所发展或是发生改变。

    遇到一个问题,需要外部调用父类的方法,怎么搞?

    class Father { public void name() { System.out.println("father"); } } class Son extends Father { public void name() { System.out.println("son"); } } 

    如果 new Son()的话,

    Son a = new Son(); Father b = new Son(); 

    甭管 a 还是 b ,调用 name()都会显示 son 。

    有没有什么办法,使得 b 调用 name()得到 father ?

    BTW ,Son 可能还有其他方法需要 b 来调用。 再 BTW ,不限于使用反射来搞定。

    第 1 条附言    2022 年 1 月 30 日

    好吧,我认为这个已经无解了。

    然后描述一下场景。 类B继承自类A,重写了A的m方法,然后B的m方法内部写了super.m()来调用A的m(),这都没有问题。A和B都是库文件,原则上不能动。

    但是现在来了一个类C,一定程度上在模拟B的其他方法,其中就调用了m(),但是因为B的m除了super.m()还加料了,而在C的处理过程中,不希望加料,也就是能够调用到A的m()对了A是abstract抽象类不能实例,m也不是static。C可以随便动。

    大抵就是:

    abstract class A { public void m() {...} } class B extends B { public void m() { super.m(); do sth. else; } public void s() { m(); do x; do y; } } class C { public void x() { // similar to B.s() B b = new B(); B.m(); undo sth. else; do x; do y; } } 

    于是我现在的做法就是在C上先经过B的加料,再专门减料。算是贴了一个大补丁凑合怼过去了。

    39 条回复    2022-02-09 13:21:49 +08:00
    golangLover
        1
    golangLover  
       2022 年 1 月 30 日 via Android
    在 son 里面写 super.name()
    cpstar
        2
    cpstar  
    OP
       2022 年 1 月 30 日
    @golangLover 必须是 b 调用,外部调用,不是内部
    Building
        3
    Building  
       2022 年 1 月 30 日
    这是一个类调用同一个方法返回不同结果了啊,无解的,调用同一个方法返回的东西一定是一样的。
    littlewing
        4
    littlewing  
       2022 年 1 月 30 日
    貌似没办法
    换 C++ 吧
    golangLover
        5
    golangLover  
       2022 年 1 月 30 日 via Android
    @cpstar 那不会了。为什么提供多一个不同名的方法不行呢?
    Jooooooooo
        6
    Jooooooooo  
       2022 年 1 月 30 日
    感觉是 xy 问题啊, 要不细说说究竟是什么场景需要这样.
    ccde8259
        7
    ccde8259  
       2022 年 1 月 30 日 via iPhone
    首先得让 Father 去实现一个具有 name 方法的 interface ,然后是反射重写对象头把 Son 实例的 Class ptr 指向 Father 。
    lsry
        8
    lsry  
       2022 年 1 月 30 日
    如果是想让 father 调用自己的方法(以变量定义时候的类型,而不是实际类型),可以将方法前面加 static 。
    Leviathann
        9
    Leviathann  
       2022 年 1 月 30 日
    子类型是新的类型啊
    又不是 javascrpit 存了父类型的原型
    SoloCompany
        10
    SoloCompany  
       2022 年 1 月 30 日
    简单来说是不可能的, 你必须修改 A (父类) 或 B (子类) 提供一个非多态的 alias 才能调用到
    SoloCompany
        11
    SoloCompany  
       2022 年 1 月 30 日
    再详细一些, 反射只能 hack field / method 的不可见或不可访问的问题而无法 hack method 因多态而 hidden 的问题, 真实的 hack 只能把想调用的方法重新写一次, 可以利用反射越过无法访问的 filed / method
    bigbyto
        12
    bigbyto  
       2022 年 1 月 30 日 via iPhone
    invokedymanic 可以实现,不过尽可能不要这样做。
    crayygy
        13
    crayygy  
       2022 年 1 月 30 日
    外部有什么必须要调用父类方法的理由吗?一般涉及到这类很 tricky 的问题的时候从需求侧开始思考问题比较合适,也许有更合适的其他方法
    251
        14
    251  
       2022 年 1 月 30 日
    既然重写了 name(),就说明 son.name()能完全代替 father.name()的功能,所以不需要调 father.name().如果不能完全替代,说明 father.nameson.name() 功能不一样,那就不要重写了。C++ 应该可以,java 不知道。
    251
        15
    251  
       2022 年 1 月 30 日
    刚才又想了一下,肯定行。但这个问题有点无聊了,a 跟 b 是同一个对象,同一个对象调用签名一样的方法,jvm 默认调第二方法,但你又想掉第一个?好比 两个都叫翠花,你就叫一声翠花,鬼知道你想默认调用那个翠花?你可能想要的是想通过声明的方式不同以改变调用不用的方法,那你自己把字节码编译好,用 classloader 的 defineclass 加载,肯定可以。
    pptom
        16
    pptom  
       2022 年 1 月 30 日
    这就是多态啊,只有运行时才确定对象是哪个类的实例。不理解这个问题的意义
    EvanLuo42
        17
    EvanLuo42  
       2022 年 1 月 30 日 via iPhone
    所以为什么要 b 调 a ,这样做用继承又有什么用。你 b 单方面的调用一个子类,从我的观点来看违背了 oop 的思想。
    Dockerfile
        18
    Dockerfile  
       2022 年 1 月 30 日
    单看代码你直接 new Father() 不就完了
    cpstar
        19
    cpstar  
    OP
       2022 年 1 月 30 日
    @littlewing 4# 提问题之前搜了一下,说到了 C++可以 upcast 到父类,就能用父类的方法覆盖子类。然后就想起来当初学习 JAVA 时,就说 C++不纯粹面向对象,这就是其中一个问题,做不到这个层面的多态。
    cpstar
        20
    cpstar  
    OP
       2022 年 1 月 30 日
    @251 15# 我感觉也应该行,而且我还知道 jvm 上,类加载之后,除了子类加载到内存中并且具有方法指针入口,父类也会加载到内存里,只不过多态调用的时候,实例中的方法入口指针指向的是子类的(因为实例是子类的),所以我在问题里提到了反射(反射没有系统学过,不太清楚怎么用)。

    按照 @SoloCompany 11#,我能理解反射能够做到的,也仅是“类”这个层面的,再结合 @bigbyto 12#提到的 invokedynamic ,这个应该是需要渗透到 jvm 层面操控“实例”来搞定了(比如修改实例的方法入口指针),某种程度上应该就是 reflect jvm 。擦,怎么感觉有点像 matrix 了,neo 要苏醒, rz 。。。
    251
        21
    251  
       2022 年 1 月 30 日   1
    行肯定行,要改字节码,你愿意折腾可以看看(我没看) http://rohandhapodkar.blogspot.com/2012/03/call-grand-parent-method-in-java.html 。说一下我的思路:先编译字节码->反编译字节码->改字节码->编译成字节码
    ipwx
        22
    ipwx  
       2022 年 1 月 31 日
    @cpstar 根本不用 upcast ,C++ 直接用随便用。

    https://godbolt.org/z/fbPv4srsv
    bigbyto
        23
    bigbyto  
       2022 年 1 月 31 日 via iPhone   1
    不是很麻烦,mybatis 有类似的调用接口,贴一段代码你参考。不过尽量别这么做就是了。

    public static void main(String[] args) throws Throwable {
    Father f = new Son();
    MethodType mt = MethodType.methodType(void.class);
    Field impl = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
    impl.setAccessible(true);
    MethodHandle mh = ((MethodHandles.Lookup) impl.get(null)).findSpecial(Father.class,"name",mt,Son.class);
    mh.invoke(f);
    }
    bigbyto
        24
    bigbyto  
       2022 年 1 月 31 日 via iPhone
    这问题麻烦的点在于 jvm 方法调用都是使用 invokevirtual 指令,这条指令的分派逻辑是固定不变的。MethodHandle 提供了一些动态特性,可以使用 invokedynamic 来执行动态分派逻辑。
    clf
        25
    clf  
       2022 年 1 月 31 日
    emmm ,所以为啥会有这样的需求???

    其实是可以的,只是你别当作是私有方法即可,直接静态方法,通过对象去调用静态方法 IDE 虽然会 warning ,但可以无视(既然你有这样诡异的需求)

    参考如下:

    public class Father {
    public static void name(){
    System.out.println("father");
    }
    }

    public class Son extends Father {
    public static void name(){
    System.out.println("son");
    }
    }

    测试:
    Father father = new Son();
    Son son = new Son();
    father.name();
    son.name();

    输出:
    father
    son
    clf
        26
    clf  
       2022 年 1 月 31 日
    在调用 name 的时候本质上是调用了 Father 和 Son 的静态方法。

    如果你想要的是一个根据自身属性输出不同结果,而 Son 有一种算法,Father 有另外一种算法,而且所涉及的内部属性父类子类均有,那么建议变成:
    public class Father {
    public static String demo(Father f){
    //这里是父类计算方法
    }
    }

    public class Son extends Father {
    public static String demo(Father f){
    //这里是子类计算方法
    }
    }

    测试:
    Father father = new Son();
    Son son = new Son();
    father.demo(father);
    son.demo(son);
    xuanbg
        27
    xuanbg  
       2022 年 1 月 31 日
    继承就是这样被玩坏的。我一向只把基类当作子类的抽象,绝不搞什么乱七八糟的多态。
    cpstar
        28
    cpstar  
    OP
       2022 年 1 月 31 日
    @clf 看一下追加,A 、B ( Father 、Son )是库类,不能轻易动的,所以只能在实例上和 C 上做文章。

    @xuanbg 面向对象三大特性,封装、继承、多态
    iseki
        29
    iseki  
       2022 年 1 月 31 日 via Android
    看了下你的附言,正确的做法大概是直接集成类 A ,自己实现一部分功能。出这种问题要么是你对这俩类的使用方法不恰当,要么就是这俩类本身封装的不好,才会让你需要做这么奇怪的事。
    C++下方法可以是虚的也可以不是,Java 都是虚的就没正常办法了…
    aliveyang
        30
    aliveyang  
       2022 年 1 月 31 日
    无解,子与父分明是两个对象
    0TSH60F7J2rVkg8t
        31
    0TSH60F7J2rVkg8t  
       2022 年 1 月 31 日
    C 类不可以用 Wapper 设计模式吗?不谈继承,C 类有 A,B 两个类共用的公开方法,在 C 类里保存一个 B 类实例,所有的 C 类公开方法调用 B 类的公开方法返回,唯独 C.Name 的时候,自己来个实现不使用 B 的方法。如果是需要 C.Name 来自 A.Name ,那么一个 C 类实例就维护 A 类和 B 类两个实例化的对象不就行了吗?当然这个时候,C 类既可以和 AB 类完全没有继承关系,也可以让 C 类从 A 或者 B 继承下来。如果从 A 或者 B 继承,那么你就只需要单独维护另外一个类的实例即可(有点绕,就是假设 C 从 B 继承,实例化的时候创建一个 A 的对象自己引用,调用 C.Name 的时候不从 supper 访问,直接访问 A.Name 返回。同理,如果 C 从 A 继承,那就维护一个 B 的对象引用即可)。
    ychost
        32
    ychost  
       2022 年 1 月 31 日
    C# 可以,Java 是 [静态多分派] ,而 C# 是 [动态单分派]
    ychost
        33
    ychost  
       2022 年 1 月 31 日
    @ychost 动态多分派
    MrKrabs
        34
    MrKrabs  
       2022 年 2 月 1 日
    为什么不直接 new 一个 Father 呢
    bololobo
        35
    bololobo  
       2022 年 2 月 3 日
    31 楼正解
    bololobo
        36
    bololobo  
       2022 年 2 月 3 日
    @bololobo 这种需求用继承来实现不太合适
    1194129822
        37
    1194129822  
       2022 年 2 月 6 日
    @bigbyto 你这种方法是错的,Java 类模型,子类或者类外无法访问祖类被重写方法。你这方法是 java7 某个版本的 bug ,在《深入理解 Java 虚拟机》里面也有你这例子,但是 R 大后来去问 JVM 相关开发者,确认这是 bug ,之后就修复了。修改字节码 invokevirtual 变成 invokespecial 也只是访问父类方法,也无法访问祖类方法。
    ikas
        38
    ikas  
       2022 年 2 月 8 日
    java 是简化了这种继承的调用,你基本可以无脑的调用,而不是注意到底是谁实现 /重写的

    如果你用 c#或者其他一些语言,你就要分清
    Joker123456789
        39
    Joker123456789  
       2022 年 2 月 9 日   1
    这样就好了:Father b = new Father();

    其他都是歪门邪道。

    你都 new Son 了,却希望他是 Father , 自己好好想想这个想法对吗?
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     995 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 79ms UTC 22:57 PVG 06:57 LAX 15:57 JFK 18:57
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86