遇到一个问题,需要外部调用父类的方法,怎么搞?
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 ,不限于使用反射来搞定。
好吧,我认为这个已经无解了。
然后描述一下场景。 类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的加料,再专门减料。算是贴了一个大补丁凑合怼过去了。
1
golangLover 2022-01-30 17:46:24 +08:00 via Android
在 son 里面写 super.name()
|
2
cpstar OP @golangLover 必须是 b 调用,外部调用,不是内部
|
3
Building 2022-01-30 18:35:19 +08:00
这是一个类调用同一个方法返回不同结果了啊,无解的,调用同一个方法返回的东西一定是一样的。
|
4
littlewing 2022-01-30 18:36:27 +08:00
貌似没办法
换 C++ 吧 |
5
golangLover 2022-01-30 18:48:56 +08:00 via Android
@cpstar 那不会了。为什么提供多一个不同名的方法不行呢?
|
6
Jooooooooo 2022-01-30 18:49:30 +08:00
感觉是 xy 问题啊, 要不细说说究竟是什么场景需要这样.
|
7
ccde8259 2022-01-30 19:19:21 +08:00 via iPhone
首先得让 Father 去实现一个具有 name 方法的 interface ,然后是反射重写对象头把 Son 实例的 Class ptr 指向 Father 。
|
8
lsry 2022-01-30 19:30:37 +08:00
如果是想让 father 调用自己的方法(以变量定义时候的类型,而不是实际类型),可以将方法前面加 static 。
|
9
Leviathann 2022-01-30 19:59:55 +08:00
子类型是新的类型啊
又不是 javascrpit 存了父类型的原型 |
10
SoloCompany 2022-01-30 20:38:18 +08:00
简单来说是不可能的, 你必须修改 A (父类) 或 B (子类) 提供一个非多态的 alias 才能调用到
|
11
SoloCompany 2022-01-30 20:41:13 +08:00
再详细一些, 反射只能 hack field / method 的不可见或不可访问的问题而无法 hack method 因多态而 hidden 的问题, 真实的 hack 只能把想调用的方法重新写一次, 可以利用反射越过无法访问的 filed / method
|
12
bigbyto 2022-01-30 20:42:26 +08:00 via iPhone
invokedymanic 可以实现,不过尽可能不要这样做。
|
13
crayygy 2022-01-30 20:45:29 +08:00
外部有什么必须要调用父类方法的理由吗?一般涉及到这类很 tricky 的问题的时候从需求侧开始思考问题比较合适,也许有更合适的其他方法
|
14
251 2022-01-30 21:20:12 +08:00
既然重写了 name(),就说明 son.name()能完全代替 father.name()的功能,所以不需要调 father.name().如果不能完全替代,说明 father.name 跟 son.name() 功能不一样,那就不要重写了。C++ 应该可以,java 不知道。
|
15
251 2022-01-30 22:04:17 +08:00
刚才又想了一下,肯定行。但这个问题有点无聊了,a 跟 b 是同一个对象,同一个对象调用签名一样的方法,jvm 默认调第二方法,但你又想掉第一个?好比 两个都叫翠花,你就叫一声翠花,鬼知道你想默认调用那个翠花?你可能想要的是想通过声明的方式不同以改变调用不用的方法,那你自己把字节码编译好,用 classloader 的 defineclass 加载,肯定可以。
|
16
pptom 2022-01-30 22:22:36 +08:00
这就是多态啊,只有运行时才确定对象是哪个类的实例。不理解这个问题的意义
|
17
EvanLuo42 2022-01-30 22:23:58 +08:00 via iPhone
所以为什么要 b 调 a ,这样做用继承又有什么用。你 b 单方面的调用一个子类,从我的观点来看违背了 oop 的思想。
|
18
Jwyt 2022-01-30 22:38:31 +08:00
单看代码你直接 new Father() 不就完了
|
19
cpstar OP @littlewing 4# 提问题之前搜了一下,说到了 C++可以 upcast 到父类,就能用父类的方法覆盖子类。然后就想起来当初学习 JAVA 时,就说 C++不纯粹面向对象,这就是其中一个问题,做不到这个层面的多态。
|
20
cpstar OP @251 15# 我感觉也应该行,而且我还知道 jvm 上,类加载之后,除了子类加载到内存中并且具有方法指针入口,父类也会加载到内存里,只不过多态调用的时候,实例中的方法入口指针指向的是子类的(因为实例是子类的),所以我在问题里提到了反射(反射没有系统学过,不太清楚怎么用)。
按照 @SoloCompany 11#,我能理解反射能够做到的,也仅是“类”这个层面的,再结合 @bigbyto 12#提到的 invokedynamic ,这个应该是需要渗透到 jvm 层面操控“实例”来搞定了(比如修改实例的方法入口指针),某种程度上应该就是 reflect jvm 。擦,怎么感觉有点像 matrix 了,neo 要苏醒,囧 rz 。。。 |
21
251 2022-01-30 23:41:04 +08:00 1
行肯定行,要改字节码,你愿意折腾可以看看(我没看) http://rohandhapodkar.blogspot.com/2012/03/call-grand-parent-method-in-java.html 。说一下我的思路:先编译字节码->反编译字节码->改字节码->编译成字节码
|
22
ipwx 2022-01-31 00:04:33 +08:00
|
23
bigbyto 2022-01-31 00:23:20 +08:00 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); } |
24
bigbyto 2022-01-31 00:29:18 +08:00 via iPhone
这问题麻烦的点在于 jvm 方法调用都是使用 invokevirtual 指令,这条指令的分派逻辑是固定不变的。MethodHandle 提供了一些动态特性,可以使用 invokedynamic 来执行动态分派逻辑。
|
25
clf 2022-01-31 02:58:44 +08:00
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 |
26
clf 2022-01-31 03:05:48 +08:00
在调用 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); |
27
xuanbg 2022-01-31 08:36:50 +08:00
继承就是这样被玩坏的。我一向只把基类当作子类的抽象,绝不搞什么乱七八糟的多态。
|
28
cpstar OP |
29
iseki 2022-01-31 15:06:32 +08:00 via Android
看了下你的附言,正确的做法大概是直接集成类 A ,自己实现一部分功能。出这种问题要么是你对这俩类的使用方法不恰当,要么就是这俩类本身封装的不好,才会让你需要做这么奇怪的事。
C++下方法可以是虚的也可以不是,Java 都是虚的就没正常办法了… |
30
aliveyang 2022-01-31 16:45:17 +08:00
无解,子与父分明是两个对象
|
31
0TSH60F7J2rVkg8t 2022-01-31 17:27:10 +08:00
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 的对象引用即可)。
|
32
ychost 2022-01-31 17:53:33 +08:00
C# 可以,Java 是 [静态多分派] ,而 C# 是 [动态单分派]
|
34
MrKrabs 2022-02-01 01:05:14 +08:00
为什么不直接 new 一个 Father 呢
|
35
bololobo 2022-02-03 12:09:27 +08:00
31 楼正解
|
37
1194129822 2022-02-06 11:05:53 +08:00
@bigbyto 你这种方法是错的,Java 类模型,子类或者类外无法访问祖类被重写方法。你这方法是 java7 某个版本的 bug ,在《深入理解 Java 虚拟机》里面也有你这例子,但是 R 大后来去问 JVM 相关开发者,确认这是 bug ,之后就修复了。修改字节码 invokevirtual 变成 invokespecial 也只是访问父类方法,也无法访问祖类方法。
|
38
ikas 2022-02-08 13:49:29 +08:00
java 是简化了这种继承的调用,你基本可以无脑的调用,而不是注意到底是谁实现 /重写的
如果你用 c#或者其他一些语言,你就要分清 |
39
Joker123456789 2022-02-09 13:21:49 +08:00 1
这样就好了:Father b = new Father();
其他都是歪门邪道。 你都 new Son 了,却希望他是 Father , 自己好好想想这个想法对吗? |