我目前正在使用Java ASM5生成一些代码,我想知道为什么我可以在我的参数上调用一个接口方法,该参数只声明为java / lang / Object类型。
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "(Ljava/lang/Object;)V", null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKEINTERFACE, "org/mydomain/Foo", "foo", "()V", true);
mv.visitMethodInsn(INVOKEINTERFACE, "org/mydomain/Bar", "bar", "()V", true);
mv.visitInsn(RETURN);
mv.visitMaxs(-1,-1);
mv.visitEnd();
一般来说,我希望这个代码在调用方法之前需要额外的强制转换,以确保该对象真正实现了这个接口。 如果我保证这个Object真的实现了这个接口,或者我可能遇到一些陷阱,那么在没有额外强制转换的情况下调用方法是否安全? VM的类型检查似乎并不关心它。如果我使用Object调用它,但是没有实现接口,我会得到一个java.lang.IncompatibleClassChangeError,这并不奇怪。 我可能会有性能损失吗?
答案 0 :(得分:2)
从JVM spec开始,只要您传入的对象实现了接口,invokeinterface
指令就会正常工作。
您不会有任何性能损失,因为invokeinterface
指令将检查类型和方法签名,即使它前面有checkcast
指令 - here is the JVM source which does the check for reference。
答案 1 :(得分:2)
您遇到了HotSpot验证程序的已知问题,而不是验证invokeinterface
调用的接收器类型的类型兼容性。但这并不意味着这样的代码在形式上是正确的。
JVMSpec, §4.9.2 Structural Constraints州:
- 作为方法调用指令目标的每个类实例的类型必须与指令中指定的类或接口类型兼容(JLS§5.2)。
但是,对于遵循该算法的旧JVM的验证程序,静态验证此约束存在实际问题,该算法现在称为“按类型推断进行验证”,并且仍然是版本低于50的类文件的标准。{{3解释了这个问题。如果必须在分支之后合并两个不同的引用类型,则可能导致在两种类型实际执行时不实现接口的公共超类型。因此,拒绝后续invokeinterface
调用可能导致拒绝正确的代码。
从版本50开始,有“按类型检查验证”,对于高于50的版本甚至是强制性的。它使用堆栈映射表,明确声明类型合并的假定结果,从而消除了昂贵的操作。因此,“按类型检查进行验证”具有This answer,它要求静态类型与接口类型兼容:
invokeinterface
如果满足以下所有条件,则 invokeinterface 指令是类型安全的:
...
- 可以使用
MethodIntfName
中给出的返回类型有效地替换与Descriptor
类型匹配的类型和传入操作数堆栈中Descriptor
中给出的参数类型,从而产生传出类型状态。
(请注意,这些规则与formal rules相同,只是它使用MethodIntfName
代替MethodClassName
)
作为旁注,在java/lang/Object
碰巧同时实施org/mydomain/Foo
和org/mydomain/Bar
的环境中,代码是正确的。这只是对这里缺少的实际环境的验证。
说实话,你的代码由于缺少检查而在HotSpot上运行,但是不可移植,即可能在其他JVM上失败,甚至可能在未来版本的HotSpot上失败,其中强制执行类型检查规则。
省略checkcast
没有性能优势,因为会有一种方式检查,如果类型不匹配则会抛出throwable。因此,我会坚持创建正式的正确代码,即使这个特定的JVM没有强制执行它。