我知道可编译的编程语言 Java 并不等同于 Java 字节码格式,用于 JVM 执行。有些东西在 .class 格式中是有效的,但在 .java 源代码中无效,例如无构造函数类和合成方法。
如果我们手工制作一个带有保留 Java 语言关键字(例如
int
、while
)作为类、方法或字段名称的 .class 文件,Java 虚拟机是否会接受它进行加载?如果该类已被加载,是否意味着访问该类或成员的唯一方式是通过 Java 反射,因为名称在 Java 编程语言中是语法非法的?
我知道可编译的编程语言 Java 并不等同于 Java 字节码格式,用于 JVM 执行。有些东西在 .class 格式中是有效的,但在 .java 源代码中无效,例如无构造函数类和合成方法。
如果我们手工制作一个带有保留 Java 语言关键字(例如 int
、while
)作为类、方法或字段名称的 .class 文件,Java 虚拟机是否会接受它进行加载?
如果该类已被加载,是否意味着访问该类或成员的唯一方式是通过 Java 反射,因为名称在 Java 编程语言中是语法非法的?
是的,您可以使用保留字。这些单词仅供编译器使用,不会出现在生成的字节码中。
在基于JVM的Scala语言中,有一个使用Java保留字的示例。Scala具有不同的结构和语法,但编译为可在JVM上运行的Java字节码。
下面是合法的Scala代码:
class `class`
这定义了一个名为class
的类,并带有一个无参构造函数。运行已编译的class.class
文件的反汇编器javap
显示:
public class class {
public class();
}
class int
class `while`
class goto
它们也可以用于方法或字段名称。
正如你所猜测的那样,除了反射,你无法从Java中使用这些类。你可以从类似“定制”的类文件中使用它们,例如从Scala编译器生成的类文件。
总之,这是javac(编译器)的限制,而不是java(VM /运行时环境)的限制。
-c
标志,它将实际进行反汇编,而不仅仅显示结构。 - Tobias[
, .
, 或 ;
,长度最多为65535个字节。这意味着您可以自由使用保留字、空格、特殊字符、Unicode甚至像换行符之类的奇怪字符。; Entry point for the jar
.class Main
.super java/lang/Object
.method public static main : ([Ljava/lang/String;)V
.limit stack 10
.limit locals 10
invokestatic int hello ()V
invokestatic "-42" hello ()V
invokestatic "" hello ()V
invokestatic " some whitespace and \t tabs" hello ()V
invokestatic "new\nline" hello ()V
invokestatic 'name with "Quotes" in it' hello ()V
return
.end method
.end class
.class int
.super java/lang/Object
.method public static hello : ()V
.limit stack 2
.limit locals 0
getstatic java/lang/System out Ljava/io/PrintStream;
ldc "Hello from int"
invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
return
.end method
.end class
.class "-42"
.super java/lang/Object
.method public static hello : ()V
.limit stack 2
.limit locals 0
getstatic java/lang/System out Ljava/io/PrintStream;
ldc "Hello from -42"
invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
return
.end method
.end class
; Even the empty string can be a class name!
.class ""
.super java/lang/Object
.method public static hello : ()V
.limit stack 2
.limit locals 0
getstatic java/lang/System out Ljava/io/PrintStream;
ldc "Hello from "
invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
return
.end method
.end class
.class " some whitespace and \t tabs"
.super java/lang/Object
.method public static hello : ()V
.limit stack 2
.limit locals 0
getstatic java/lang/System out Ljava/io/PrintStream;
ldc "Hello from some whitespace and \t tabs"
invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
return
.end method
.end class
.class "new\nline"
.super java/lang/Object
.method public static hello : ()V
.limit stack 2
.limit locals 0
getstatic java/lang/System out Ljava/io/PrintStream;
ldc "Hello from new\nline"
invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
return
.end method
.end class
.class 'name with "Quotes" in it'
.super java/lang/Object
.method public static hello : ()V
.limit stack 2
.limit locals 0
getstatic java/lang/System out Ljava/io/PrintStream;
ldc "Hello from name with \"Quotes\" in it"
invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
return
.end method
.end class
Hello from int
Hello from -42
Hello from
Hello from some whitespace and tabs
Hello from new
line
Hello from name with "Quotes" in it
请查看Holger的回答,获取来自JVM规范的准确规则引用。
有关名称的限制在JVM规范中已经确定:
在类文件结构中出现的类和接口名称始终以完全限定形式表示,称为二进制名称 (JLS §13.1)。这些名称始终表示为 CONSTANT_Utf8_info
结构 (§4.4.7),因此可以从整个 Unicode 码空间中提取,除非另有约束…
由于历史原因,出现在类文件结构中的二进制名称的语法与 JLS §13.1 中记录的二进制名称的语法不同。在这种内部形式中,通常用于组成二进制名称的标识符的 ASCII 句点 (.
) 被 ASCII 正斜杠 (/
) 替换。这些标识符本身必须是未限定名称 (§4.2.2)。
方法、字段、局部变量和形式参数的名称存储为未限定名称。未限定名称必须包含至少一个 Unicode 代码点,并且不能包含任何 ASCII 字符 .
;
[
/
(即句点或分号或左方括号或正斜杠)。
方法名称进一步受到限制,除了特殊方法名称 <init>
和 <clinit>
(§2.9) 之外,它们不能包含 ASCII 字符 <
或 >
(即左尖括号或右尖括号)。
/
是包分隔符。然后,;
和 [
不能使用,因为它们在字段签名和方法签名中具有特殊含义,这些签名可能包含类型名称。在这些签名中,[
开始一个数组类型,而;
则标记引用类型名称的结尾。.
。它不在JVM中使用,只有在通用签名中才具有意义,但是如果您正在使用通用签名,则类型名称受到进一步限制,不能包含<
、>
、:
,因为这些字符在通用签名中也具有特殊含义。.
违反规范对JVM的主要功能没有影响。有些混淆器会这样做。生成的代码可以运行,但是当要求泛型类型签名时,可能会遇到Reflection问题。此外,通过将所有/
替换为.
来将二进制名称转换为源名称,如果二进制名称包含.
,则将变得不可逆。
可能有趣的是,曾经有一个支持Java语法中所有可能标识符的提案(参见第3点,“奇异标识符”),但它没有被纳入最终的Java 7。而且似乎目前没有人正在尝试将其引入。
另外还有一个技术限制,即名称的Modified UTF-8表示长度不能超过65535个字节,因为字节数以无符号短整型值存储。
do
、while
等,以使反编译代码更加困难。(仍然可以反编译,但生成的代码基本上是无用的) - Marco13