我知道java -可编译编程语言与java -字节码格式用于jvm执行不是一回事。有些东西在。class格式中有效,但在。java源代码中无效,例如无构造函数类和合成方法。
-
如果我们用保留的Java语言关键字(例如
int
,while
)作为类名,方法名或字段名手工制作一个。class文件, Java虚拟机会接受它来加载吗? -
如果类被加载,是否意味着访问该类或成员的唯一方法是通过Java反射,因为这个名称在Java编程语言中是语法上非法的?
可以使用保留字。这些词仅供编译器使用。它们不会出现在生成的字节码中。
使用Java保留字的一个示例是基于jvm的Scala语言。Scala有不同于Java的结构和语法,但是可以编译成Java字节码,以便在JVM上运行。
这是合法的Scala:
class `class`
这定义了一个名为class
的类,带有一个无参数构造函数。在编译后的class.class
文件上运行javap
(一个反汇编器)显示
public class class {
public class();
}
Scala可以对任何其他Java保留字执行相同的操作。
class int
class `while`
class goto
也可用于方法名或字段名。
正如您所怀疑的,除了反射之外,您将无法在Java中使用这些类。可以从类似的"自定义"类文件中使用这些,例如从Scala编译器生成的类文件中使用。
总之,这是javac(编译器)的限制,而不是java (VM/运行时环境)的限制。在字节码级别对类名的唯一限制是它们不能包含字符[
, .
或;
,并且它们最多65535字节长。除此之外,这意味着您可以自由地使用保留字、空格、特殊字符、Unicode,甚至像换行符这样奇怪的东西。
理论上,您甚至可以在类名中使用空字符,但由于文件名中不可能有空字符,因此您不能在jar中包含这样的类文件。但是,您可能能够动态地创建和加载一个。
下面是你可以做的一些事情的例子(用Krakatau汇编编写):
; 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 "newnline" 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 "newnline"
.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 newnline"
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规范中是固定的:
§4.2.1。准备二进制类和接口名
出现在类文件结构中的类名和接口名总是以称为二进制名的完全限定形式表示(JLS§13.1)。这样的名称总是表示为
CONSTANT_Utf8_info
结构(§4.4.7),因此可以从整个Unicode代码空间中绘制,如果没有进一步的约束…由于历史原因,出现在类文件结构中的二进制名称的语法与JLS§13.1中记录的二进制名称的语法不同。在这种内部形式中,通常用来分隔组成二进制名称的标识符的ASCII句点(
.
)被ASCII正斜杠(/
)所取代。标识符本身必须是非限定名(第4.2.2节)。§4.2.2。不合格名称
方法、字段、局部变量和形式参数的名称存储为非限定名。非限定名必须包含至少一个Unicode码点,并且不能包含任何ASCII字符
.
;
[
/
(即,句点或分号或左方括号或正斜杠)。方法名被进一步约束,除了特殊的方法名
<init>
和<clinit>
(§2.9)之外,它们不能包含ASCII字符<
或>
(即左尖括号或右尖括号)。
所以答案是,只有少数字符不能在二进制级别上使用。首先,/
是包分隔符。然后,;
和[
不能使用,因为它们在字段签名和方法签名中有特殊的含义,可能包含类型名。在这些签名中,[
标志一个数组类型的开始,;
标志一个引用类型名称的结束。
没有明确的理由禁止.
。它不在JVM中使用,只在泛型签名中有意义,但是如果您使用泛型签名,则类型名称受到进一步限制,不允许包含<
, >
, :
以及这些字符在泛型签名中也具有特殊意义。
因此,在标识符中使用.
违反规范对JVM的主要功能没有影响。有一些混淆者在这样做。生成的代码可以工作,但是在请求泛型类型签名时,您可能会遇到反射问题。此外,如果二进制名称中包含.
s,则通过将所有/
s替换为.
s来将二进制名称转换为源名称将变得不可逆。
有一个建议支持Java语法中所有可能的标识符(参见第3点,"外来标识符"),这可能很有趣,但它没有进入最终的Java 7。而且,目前似乎没有人试图将其引入。
还有一个额外的技术限制,即名称不能有修改的UTF-8表示长度超过65535字节,因为字节数被存储为无符号短值。
- 关键字只有编译器知道。编译器翻译将它们转换成适当的字节码。所以它们在运行时不存在编译后的字节码,因此不会被JVM验证。
- 当然,您不能访问不知道的类成员编译时间。但是你可以使用反射来达到这个目的确保这样的类成员将存在于编译后的代码中是否会"手工制作"它们),因为通过反射访问是不可能的