我不记得曾经在Java字节码中看到任何引用变量类型的概念。我对类型擦除有点了解,但是这个术语似乎与泛型紧密相关,而我的问题一般是关于对象引用变量。 Java对象引用变量类型是否在编译后存活?或者变量类型只是起到帮助编译器帮助开发人员检查代码是否有意义的作用? 如果引用变量类型在编译中存在,它们在字节码中出现在哪里?
编辑: 请允许我在此感谢您的宝贵贡献。为了更多地考虑我的想法,我想补充一个例子:
Object o = "foo";
在字节码中,变量o及其类型(Object)是否可以在任何地方表示并在运行时读取?
答案 0 :(得分:6)
是的,字节码也是类型安全的。首先,每次向下转换时都会使用一个字节代码instruction called 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
注意加载8
局部变量的指令s
。使用十六进制编辑器,我将aload_2
替换为aload_1
,因此尝试在String.length()
对象(Integer
局部变量)上调用x
:
$ 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)
可能会更糟。
最后但并非最不重要的是,有大量的操作码专用于特定的原语(浮点数,双精度数,整数等)。
答案 1 :(得分:2)
JVM加载的所有字节码都是静态类型检查,以确保它是安全的。如果没有类型检查,恶意字节码就可以将int视为指针并破解VM。
但是,仅仅因为字节码是类型安全并不意味着它有明确的输入。相反,每个值都具有由类型推断确定的隐式类型。 (从Java 7开始,还必须包含指定每个基本块开头的所有内容类型的元数据,使类型推断任务更容易)。
请注意,虽然所有内容都是typechecked,但规则比Java更宽松。例如,在字节码中,不存在布尔,字节,字符或短局部变量。它们都被编译为整数。因此,您可以使用不等于true或false的布尔值。此外,在您对它们调用方法之前,不会检查接口,因此接口变量实际上可以保存任意对象。最后,Hotspot VM允许您自由地混合byte []和boolean [] s。
答案 2 :(得分:1)
字段类型在field descriptor中编码。
局部变量的类型可以编码到LocalVariableTable attribute(或泛型类型的LocalVariableFieldTypeTable attribute)中,并且在面向Java 6的类文件中,必须在{{3}中编码}。对于较旧的类文件,JVM将使用StackMapTable attribute验证字节码的类型正确性。
因此,虽然你是正确的,字段和变量的类型没有在字节码中表示,但它们在类文件中(至少对于以Java 6或更高版本为目标的类文件)。