上次我们问了:
不少同学认识到了,「桥接方法」解决了实现类与接口参数类型不匹配的问题。
那么我们再来看一个例子:
interface TestInter<T> {
T getName(String name);
}
实现类:
class TestImpl implements TestInter<String> {
@Override
public String getName(String name) {
return null;
}
}
这次猜我的关注点在哪?
我们反编译一下TestImpl:
public java.lang.String getName(java.lang.String);
descriptor: (Ljava/lang/String;)Ljava/lang/String;
flags: ACC_PUBLIC
public java.lang.Object getName(java.lang.String);
descriptor: (Ljava/lang/String;)Ljava/lang/Object;
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
又看到了ACC_BRIDGE,ACC_SYNTHETIC,已知知识。
可以看到生成的两个方法,我们转成Java的展现形式:
- String getName(String str)
- Object getName(String str);
有没有觉得奇怪?
我贴张图,你就能看明白了:
这两个方法的方法名,参数均相同,只有返回值不同,在我们Java平时编写中是不允许的。
问题来了:
- 为何在这个场景下,允许「一个类中有方法名,参数均相同,只有返回值不同」两个方法?
- 既然class文件有两个getName,那么我们调用getName时,是如何确定调用的哪个方法呢?
更多问答 >>
-
每日一问 | 启动了Activity 的 app 至少有几个线程?
2020-10-12 00:47 -
每日一问 | 玩转 Gradle,可不能不熟悉 Transform,那么,我要开始问了。
2020-10-26 23:45 -
每日一问 | 关于 RecyclerView$Adapter setHasStableIds(boolean)的一切
2020-10-26 23:44 -
每日一问 | 属性动画与硬件加速的相遇,不是你想的那么简单?
2020-10-26 23:45 -
每日一问 | 当Unsafe遇上final,超神奇的事情发生了?
2020-11-02 00:16 -
2020-09-09 23:54
-
2020-08-26 21:11
-
2020-08-23 23:54
-
每日一问 | apply plugin: 'com.android.application' 背后发生了什么?
2020-08-16 19:56 -
每日一问| View 绘制的一个细节,如何修改 View 绘制的顺序?
2020-08-12 10:21
为什么在Java代码中定义多个【名字和参数都一样,返回值类型不同】的方法会报错呢?
因为Java语言层面,它的方法签名是不带返回值类型的,只有方法名和参数类型。所以即使两方法的返回值类型不同,它也一样当作重复声明了。也可以通过一段代码来帮助理解:Test类里面定义了两个
如果我在某个地方调用getNumber
方法,它们的名字和参数类型都完全一样,只是一个返回Double一个返回Long。getNumber
方法来获取Number对象,你怎么知道我想要调用哪一个?根本无法辨别的好吧。那为什么class文件里就可以有多个【名字和参数都一样,返回值类型不同】的方法存在?JVM是怎么把它们区分开来的?
因为在class常量池中,就已经有对这些方法的完整描述,包括方法的返回值类型。这样的话,就算方法名和参数都一样,只要返回值不同,那它们也是合法的,JVM也能正确分辨出具体调用的是哪一个。修改一下题目中的TestImpl,加个构造函数并在里面调用getName
方法:编译,然后javap -v TestImpl看下:
看构造方法里highlight的那一行:invokevirtual
的操作数#2,就是要调用的方法符号索引,可以对照着上面常量池中的信息来找到(其实右边的注释已经列出来了)完整的方法签名:#2 = #3.#18#3 = #19 = TestImpl#18 = #10:#11#10 = getName#11 = (Ljava/lang/String;)Ljava/lang/String;(还可以看到#11下面的#12就是返回值为Object的方法)把它们组合起来,就是TestImpl.getName:(Ljava/lang/String;)Ljava/lang/String;了。如果它调用的是返回值为Object的方法,那么就会有#n = #10:#12 、 #m = #3.#n,下面构造函数中的invokevirtual
指令的操作数就是#m(当然了这个方法无法在代码中直接调用到,所以是看不到这种场景的)。感谢分享!学习到了
这个问题提的好.根据这个问题,我特意复习了下.java 字节码的组成部分和含义.但是一些字节码指令还是不熟悉.
上面提的问题.为什么java 的方法签名是方法名+参数.而不是 方法名+参数+return值....假设"方法名+参数+return值" 可以成为方法的唯一标签,.有如下俩个方法
synthetic bridge
关键词官方解释:这是泛型擦除的场景
大神帮忙看下
我用javac TestImpl.java完事它提示我找不到符号TestInter,这个接口文件它找不到,在一个目录下的,我要咋搞才能生成class文件?你应该是直接在AS里面创建的类,把包名删掉就行了
是啊,我在studio的terminal里敲的命令 ,javac 绝对路径 不行,我cd到这个文件目录下,直接javac 文件名字 也不行,它提示找不到那个接口文件 ...查看更多
是啊,我在studio的terminal里敲的命令 ,javac 绝对路径 不行,我cd到这个文件目录下,直接javac 文件名字 也不行,它提示找不到那个接口文件
把2个文件一起编就行了比如: javac A.java B.java
1.对于虚拟机来说,方法的类型表示包含返回值,比如无参无返回值的方法类型是()V,参数为String,返回值为int的方法类型是(Ljava/lang/Class)I。虚拟机不允许类型相同,名字也相同的方法,这样会导致方法调用不确定,但是返回值是方法类型的一部分。
2.为什么要分辨?得到的结果是一样的。当然,得到的是非桥接的那个方法。
ps:每日一问 | Java 泛型与接口碰撞出的火花!这个例子里其实也不用分辨,当然,要是不传String会类型转换异常for (Method method : TestImpl.class.getMethods()) { if (method.getName().equals("getName ...查看更多
for (Method method : TestImpl.class.getMethods()) { if (method.getName().equals("getName") && method.isSynthetic() && method.isBridge()) { System.out.println(method); System.out.println("getName = " + method.invoke(this, "")); break; } }
这样获取到的就是那个桥接方法
我也获取了桥接方法的,通过返回值类型来过滤的,我当忘了有直接判断的方法,哈哈
马克