我不记得在Java字节码中看到过任何引用变量类型的概念。我对类型擦除略知一二,但这个术语似乎与泛型紧密相关,而我的问题是关于一般的对象引用变量。Java对象引用变量类型在编译中幸存下来吗?还是变量类型只是帮助编译器帮助开发人员检查代码是否有意义?如果引用变量类型在编译中幸存下来,它们出现在字节码中的什么位置?
编辑:请允许我在此感谢你所作的宝贵贡献。为了进一步缩小我的想法,我想添加一个例子:
Object o = "foo";
在字节码中,变量 o 及其类型(对象)是否会在任何地方表示并在运行时读取?
是的,字节码也是类型安全的。首先,有一条名为checkcast
的字节码指令,每次向下投射时都会使用:
Object obj = "abc";
String s = (String)obj;
翻译成:
aload_1
checkcast #3 // class java/lang/String
astore_2
其次,invokevirtual
和其他人期望给定类型的对象。如果您传递了错误的类型,JVM 将拒绝加载此类。我认为这在Java语言中是无法实现的,所以我做了一些黑客攻击。以下代码:
Integer x = 1;
String s = "abc";
int len = s.length();
翻译成:
0: iconst_1
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
5: ldc #3 // String abc
7: astore_2
8: aload_2
9: invokevirtual #4 // Method java/lang/String.length:()I
12: istore_3
请注意加载s
局部变量的指令8
。使用十六进制编辑器,我用aload_1
替换了aload_2
,从而尝试在Integer
对象(x
局部变量)上调用String.length()
:
$ java Test
Exception in thread "main" java.lang.VerifyError:
Bad type on operand stack in method Test.main([Ljava/lang/String;)V at offset 9
只是如果你好奇,如果你禁用类验证,地狱就会崩溃:
$ java -Xverify:none Test
Exception in thread "main" java.lang.NullPointerException
at java.lang.String.length(String.java:623)
at Test.main(Test.java:6)
可能会更糟。
最后但并非最不重要的一点是,有很多操作码专用于特定的基元(浮点数、双精度数、整数等)。
JVM加载的所有字节码都经过静态类型检查,以确保其安全。如果没有类型检查,恶意字节码可能只是将 int 视为指针并入侵 VM。
但是,仅仅因为字节码是类型安全的并不意味着它具有显式类型。相反,每个值都有一个由类型推断确定的隐式类型。(从 Java 7 开始,还必须在每个基本块的开头包含指定所有内容的类型的元数据,从而使类型推断任务变得更加容易)。
请注意,虽然所有内容都经过类型检查,但规则比 Java 更宽松。例如,在字节码中,没有布尔值、字节、字符或短局部变量这样的东西。它们都被编译为整数。因此,例如,您可以拥有一个不等于 true 或 false 的布尔值。此外,在对接口调用方法之前,不会检查接口,因此接口变量实际上可以容纳任意对象。最后,热点虚拟机允许您自由混合字节[]和布尔值[]。
类型在字段描述符中编码。
局部变量的类型可以编码到 LocalVariableTable 属性(或泛型类型的 LocalVariableFieldTypeTable 属性)中,并且在面向 Java 6 的类文件中,必须在 StackMapTable 属性中编码。对于较旧的类文件,JVM 将使用类型推断来验证字节码的类型正确性。
因此,虽然您是正确的,字段和变量的类型不是用字节码表示的,但它们在类文件中(至少对于面向 Java 6 或更高版本的类文件)。