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

2022-01-30 17:02:18 +08:00
 cpstar

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

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 ,不限于使用反射来搞定。

3566 次点击
所在节点    Java
39 条回复
251
2022-01-30 23:41:04 +08:00
行肯定行,要改字节码,你愿意折腾可以看看(我没看) http://rohandhapodkar.blogspot.com/2012/03/call-grand-parent-method-in-java.html 。说一下我的思路:先编译字节码->反编译字节码->改字节码->编译成字节码
ipwx
2022-01-31 00:04:33 +08:00
@cpstar 根本不用 upcast ,C++ 直接用随便用。

https://godbolt.org/z/fbPv4srsv
bigbyto
2022-01-31 00:23:20 +08:00
不是很麻烦,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
2022-01-31 00:29:18 +08:00
这问题麻烦的点在于 jvm 方法调用都是使用 invokevirtual 指令,这条指令的分派逻辑是固定不变的。MethodHandle 提供了一些动态特性,可以使用 invokedynamic 来执行动态分派逻辑。
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
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);
xuanbg
2022-01-31 08:36:50 +08:00
继承就是这样被玩坏的。我一向只把基类当作子类的抽象,绝不搞什么乱七八糟的多态。
cpstar
2022-01-31 09:04:09 +08:00
@clf 看一下追加,A 、B ( Father 、Son )是库类,不能轻易动的,所以只能在实例上和 C 上做文章。

@xuanbg 面向对象三大特性,封装、继承、多态
iseki
2022-01-31 15:06:32 +08:00
看了下你的附言,正确的做法大概是直接集成类 A ,自己实现一部分功能。出这种问题要么是你对这俩类的使用方法不恰当,要么就是这俩类本身封装的不好,才会让你需要做这么奇怪的事。
C++下方法可以是虚的也可以不是,Java 都是虚的就没正常办法了…
aliveyang
2022-01-31 16:45:17 +08:00
无解,子与父分明是两个对象
ahhui
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 的对象引用即可)。
ychost
2022-01-31 17:53:33 +08:00
C# 可以,Java 是 [静态多分派] ,而 C# 是 [动态单分派]
ychost
2022-01-31 17:53:44 +08:00
@ychost 动态多分派
MrKrabs
2022-02-01 01:05:14 +08:00
为什么不直接 new 一个 Father 呢
bololobo
2022-02-03 12:09:27 +08:00
31 楼正解
bololobo
2022-02-03 12:11:08 +08:00
@bololobo 这种需求用继承来实现不太合适
1194129822
2022-02-06 11:05:53 +08:00
@bigbyto 你这种方法是错的,Java 类模型,子类或者类外无法访问祖类被重写方法。你这方法是 java7 某个版本的 bug ,在《深入理解 Java 虚拟机》里面也有你这例子,但是 R 大后来去问 JVM 相关开发者,确认这是 bug ,之后就修复了。修改字节码 invokevirtual 变成 invokespecial 也只是访问父类方法,也无法访问祖类方法。
ikas
2022-02-08 13:49:29 +08:00
java 是简化了这种继承的调用,你基本可以无脑的调用,而不是注意到底是谁实现 /重写的

如果你用 c#或者其他一些语言,你就要分清
Joker123456789
2022-02-09 13:21:49 +08:00
这样就好了:Father b = new Father();

其他都是歪门邪道。

你都 new Son 了,却希望他是 Father , 自己好好想想这个想法对吗?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/831432

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX