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

Nay*_*uki 59 java reflection jvm .class-file

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

  1. 如果我们的手工工艺.class文件有保留的Java语言的关键字(例如int,while)作为类,方法或字段名,将在Java虚拟机接受它加载?

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

Pau*_*per 37

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

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

这是合法的Scala:

class `class`
Run Code Online (Sandbox Code Playgroud)

这定义了一个class用no-arg构造函数命名的类.javap在编译class.class文件上运行(反汇编程序)显示

public class class {
    public class();
}
Run Code Online (Sandbox Code Playgroud)

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

class int
class `while`
class goto
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 让我们坚持反汇编;) (3认同)
  • 非常好,我不知道Scala本身允许你滥用Java保留关键字作为名称=) (2认同)
  • 从技术上讲,它是Java语言的限制,而不是javac.任何兼容的Java编译器都将强制执行相同的限制. (2认同)

Ant*_*ony 29

字节码级别对类名的唯一限制是它们不能包含字符[,.或者;它们最多只有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
Run Code Online (Sandbox Code Playgroud)

执行输出:

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
Run Code Online (Sandbox Code Playgroud)

有关JVM规范中规则的确切引用,请参阅Holger的答案.

  • 确实有趣!你的代码使用什么语法?是否可以使用现有工具将其编译为.class文件? (3认同)

Hol*_*ger 18

有关名称的限制在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的主要功能.有混淆器这样做.生成的代码可以正常工作,但在请求通用类型签名时,您可能会遇到Reflection的问题.此外,如果二进制名称包含s,则通过用/s 替换所有s 将二进制名称转换为源名称.将变得不可逆转..


可能有趣的是,有一个提议支持Java语法中的所有可能的标识符  (参见第3点,"异国标识符"),但它没有进入最终的Java 7.看起来,目前没有人重新尝试将其带入.


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


Ser*_*nin 10

  1. 关键字只有编译器知道.编译器将它们转换为足够的字节码.因此,它们在编译的字节码的运行时期间不存在,因此不会被JVM验证.
  2. 当然,您无法访问编译时未知的类成员.但是,如果您确定这样的类成员将存在于已编译的代码中(您将在那里"手工制作"它们),则可以使用反射来实现该目的,因为编译器不会验证反射访问.