首先,有几种不同的类型概念。编译时类型包括泛型。但是,泛型在编译后不存在。
变量的静态类型可以是int、float、long、double、returnaddress或对象引用。对象引用还带有上限类型,以便所有引用都是
java/lang/String
的子类型。字段还可以具有byte、short、char或boolean等短类型。它们在执行目的上与int相同,但存储不同。
最后,运行时类型与已验证的静态类型相同,但在对象引用的情况下,表示被引用实例的实际类型。请注意,由于验证器的惰性,有些情况下运行时类型实际上可能不是已验证类型的子类型。例如,在Hotspot中,声明类型为
Comparable
的变量实际上可以保存任何对象,因为VM在验证时不检查接口。
除了反射和调试的可选属性外,编译时信息不会被保留。这是因为没有理由保留它。
局部变量没有显式类型信息(除了新的StackMapTable属性,但那只是技术细节)。相反,当类加载时,字节码验证器通过运行静态数据流分析为每个值推断类型。其目的不是像编译时类型检查那样捕获错误,因为假定字节码已经通过了这样的检查。
相反,验证的目的是确保指令对VM本身不危险。例如,它需要确保您不会将整数作为对象引用来解释,因为那可能导致任意内存访问和黑客攻击VM。
因此,虽然字节码值没有显式类型信息,但它们具有静态类型推断的隐式类型。这些细节基于每个VM的内部实现细节而有所不同,尽管它们应该遵循JVM标准。但是,您只需要在手写字节码中担心这个问题。
字段具有显式类型,因为VM需要知道其中存储的数据类型。方法参数和返回类型编码在所谓的方法描述符中,也用于类型检查。它们无法自动推断,因为这些值可以来自或去往任何地方,而类型检查是按类进行的。
补充说明一下,当谈到验证类型时,我省略了一些细节。对象类型还跟踪它们是否已初始化,以及如果未初始化,则创建它们的指令。地址类型跟踪创建它们的jsr的目标。