Java类文件可以使用保留关键字作为名称吗?

时间:2015-05-27 19:04:25

标签: java reflection jvm .class-file

我知道Java-the-compilable-programming-language与Java-the-bytecode-format-for-JVM-execution不同。有一些例子在.class格式中有效但在.java源代码中没有,例如无构造函数的类和合成方法。

  1. 如果我们使用保留的Java语言关键字(例如intwhile手工制作.class文件作为类,方法或字段name,Java虚拟机是否接受它加载?

  2. 如果加载了类,是否意味着访问此类或成员的唯一方法是通过Java反射,因为该名称在Java编程语言中在语法上是非法的?

4 个答案:

答案 0 :(得分:37)

是的,您可以使用保留字。这些单词仅适用于编译器。它们不会出现在生成的字节代码中。

使用保留Java字的示例是基于JVM的Scala语言。 Scala具有与Java不同的构造和语法,但是编译为Java字节代码,以便在JVM上运行。

这是合法的Scala:

class `class`

这定义了一个名为class的类,其中包含一个无参数构造函数。在已编译的javap文件上运行class.class(反汇编程序)显示

public class class {
    public class();
}

Scala可以对任何其他Java保留字执行相同操作。

class int
class `while`
class goto

它们也可用于方法或字段名称。

正如您所怀疑的那样,除了反射之外,您将无法使用Java中的这些类。您可以从类似的“自定义”类文件中使用这些文件,例如来自Scala编译器生成的类文件。

总之,这是javac(编译器)的限制,而不是java(VM /运行时环境)。

答案 1 :(得分:28)

字节码级别对类名的唯一限制是它们不能包含字符[.;,并且它们最多只能包含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 "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's answer以获取JVM规范中规则的确切引用。

答案 2 :(得分:18)

有关名称的限制在JVM规范中已修复:

  

§4.2.1. Binary Class and Interface Names

     

出现在类文件结构中的类和接口名称始终以称为二进制名称的完全限定形式表示(JLS§13.1)。这些名称总是表示为CONSTANT_Utf8_info结构(§4.4.7),因此可以从整个Unicode代码空间中绘制,而不是进一步约束......

     

由于历史原因,出现在类文件结构中的二进制名称的语法与JLS§13.1中记录的二进制名称的语法不同。在此内部形式中,通常将构成二进制名称的标识符分隔的ASCII句点(.)替换为ASCII正斜杠(/)。标识符本身必须是非限定名称(第4.2.2节)。   

     

§4.2.2. Unqualified Names

     

方法,字段,局部变量和形式参数的名称存储为非限定名称。非限定名称必须至少包含一个Unicode代码点,且不得包含任何ASCII字符. ; [ /(即句点或分号或左侧方格)支架或正斜杠。)

     

进一步约束方法名称,以便除特殊方法名称<init><clinit>(§2.9)外,它们不得包含ASCII字符<或{{ 1}}(即左尖括号或右尖括号)。

所以答案是,在二进制级别上只能使用几个字符。首先,>是包分隔符。然后,/;无法使用,因为field signaturesmethod signatures中可能包含类型名称具有特殊含义。在这些签名中,[启动数组类型,[标记引用类型名称的结尾。

没有明确的理由禁止;。它不在JVM中使用,只在generic signatures中有意义,但如果使用通用签名,则类型名称因不允许包含{{1}而受到进一步限制},.<以及这些字符在通用签名中也有特殊含义。

因此,在标识符中使用>违反规范对JVM的主要功能没有影响。有混淆器这样做。生成的代码可以正常工作,但在请求通用类型签名时,您可能会遇到Reflection的问题。此外,如果二进制名称包含: s,则通过将所有.替换为/来将二进制名称转换为源名称将不可逆转。

有一个proposal to support all possible identifiers within Java syntax可能很有意思(参见第3点,“异国情调的标识符”),但它没有进入最终的Java 7.而且似乎没有人正在制作将它带入的新尝试。

还有一个额外的技术限制,即名称不能使Modified UTF-8 representation长于65535字节,因为字节数存储为无符号短值。

答案 3 :(得分:10)

  1. 只有编译器知道关键字。编译器进行翻译 他们进入足够的字节码。所以它们在运行时不存在 编译后的字节码,因此不会被JVM验证。
  2. 当然,您无法访问未知的班级成员 编译时间。但是,如果你是这样,你可以使用反射 确保这样的类成员将存在于已编译的代码中(您 将“手工制作”他们在那里),因为反射访问不是 由编译器验证。