JVM在校验阶段不检查接口的实现状况2012-01-03 javaeye RednaxelaFX继续看到底要运行一个Java程序需要做的各种检查是在什么时候发生的。这次我们来看看接口调用的问题。当前的JVM规范中,与方法调用相关的指令有4个:invokevirtual、invokeinterface、invokestatic与invokespecial。其中调用接口方法时使用的JVM指令是invokeinterface。这个指令与另外3个方法调用指令有一个显著的差异:它不要求JVM的校验器(verifier)检查被调用对象(receiver)的类型;另外3个方法调用指令都要求校验被调用对象。也就是说,使用invokeinterface时如果被调用对象没有实现指定的接口,则应该在运行时而不是链接时抛出异常;而另外3个方法调用指令都要求在链接时抛出异常。看看JVM规范是怎么说的:Java Virtual Machine Specification, 2nd Edition 写道invokeinterface...Runtime Exceptions...if the class of objectref does not implement the resolved interface, invokeinterface throws an IncompatibleClassChangeError.可以留意一下另外3个方法调用指令中“IncompatibleClassChangeError”都是Linking Exception而不是Runtime Exception。这种规定对Java程序来说可见的行为就是:如果一个方法通不过校验,则整个方法都不会被执行;如果能通过校验而抛出运行时异常,则方法当中抛出异常之前的部分都会被执行。当然,我们直接用Java语言写出来的程序很难引发这样的错误,因为Java编译器会做检查来保证一定程度的类型安全。但是Java的class文件,或者说Java字节码可以由Java编译器以外的别的方式生成,此时就得不到Java编译器对类型安全的保证,而要依赖于JVM对字节码的校验以及运行时的检查了。我是之前在读John Rose对JSR 292的invokedynamic的讲解时留意到invokeinterface的这个特点的。John特别提到invokedynamic就像invokeinterface一样,都不在校验时对被调用对象的类型做检查。不过之前一直没见过调用对一个没实现接口的对象调用接口方法实际是个什么样子。好吧,这次就来看个例子。首先创建一个接口IFoo,一个实现了该接口的类FooImpl,和一个未实现该接口的类Bar:IFoo.java:Java代码
public interface IFoo {void method();}FooImpl.java:Java代码
public class FooImpl implements IFoo {public void method() {System.out.println("FooImpl.method()");}}