本文档描述了对Java 语言规范的更改,以支持未命名类和实例main
方法,这是Java SE 21的预览功能。请参阅JEP 445以获取该功能的概述。
一个配套文档描述了对Java 虚拟机规范的更改,以支持未命名类和实例main
方法。
更改是针对JLS现有部分描述的。新文本显示为这样,删除的文本显示为
这样。必要时,解释和讨论将放在灰色框中。
变更日志:
2023-05-30: 进行了轻微的编辑更改。
2023-05-24: 杂项编辑更改。
2023-05-19: 12.1.4 更改了候选main
方法的定义,反映了JEP中的更改。
2023-05-15:
- 添加了对配套JVMS文档的引用。
- 12.1.4 改进了对候选
main
方法的解释。
2023-05-02: 发布了第一版草案
第6章:名称
6.7 完全限定名称和规范名称
每个原始类型、命名包、命名顶级类和顶级接口都有一个完全限定名称:
-
原始类型的完全限定名称是该原始类型的关键字,即
byte
、short
、char
、int
、long
、float
、double
或boolean
。 -
非命名包的命名包的完全限定名称是其简单名称。
-
作为另一个命名包的子包的命名包的完全限定名称由包含包的完全限定名称、后跟"
.
"、后跟子包的简单(成员)名称组成。 -
在未命名包中声明的命名顶级类或顶级接口的完全限定名称是类或接口的简单名称。
-
在命名包中声明的顶级类或顶级接口的完全限定名称由包的完全限定名称、后跟"
.
"、后跟类或接口的简单名称组成。
每个成员类、成员接口和数组类型可能有一个完全限定名称:
-
另一个类或接口C的成员类或成员接口M仅当C有完全限定名称时才有完全限定名称。
在这种情况下,M的完全限定名称由C的完全限定名称、后跟"
.
"、后跟M的简单名称组成。 -
数组类型仅当其元素类型有完全限定名称时才有完全限定名称。
在这种情况下,数组类型的完全限定名称由数组类型的组件类型的完全限定名称后跟"
[]
"组成。
局部类、局部接口、
或匿名类或未命名顶级类没有完全限定名称。
每个原始类型、命名包、命名顶级类和顶级接口都有一个规范名称:
- 对于每个原始类型、命名包、顶级类和顶级接口,规范名称与完全限定名称相同。
每个成员类、成员接口和数组类型可能有一个规范名称:
-
在另一个类或接口C中声明的成员类或成员接口M仅当C有规范名称时才有规范名称。
在这种情况下,M的规范名称由C的规范名称、后跟"
.
"、后跟M的简单名称组成。 -
数组类型仅当其组件类型有规范名称时才有规范名称。
在这种情况下,数组类型的规范名称由数组类型的组件类型的规范名称后跟"
[]
"组成。
局部类、局部接口、
或匿名类或未命名顶级类没有规范名称。
示例 6.7-1. 完全限定名称
-
类型
long
的完全限定名称为"long
"。 -
包
java.lang
的完全限定名称为"java.lang
",因为它是包java
的子包lang
。 -
在包
java.lang
中定义的类Object
的完全限定名称为"java.lang.Object
"。 -
在包
java.util
中定义的接口Enumeration
的完全限定名称为"java.util.Enumeration
"。 -
类型"数组
double
"的完全限定名称为"double[]
"。 -
类型"数组的数组的数组的数组的
String
"的完全限定名称为"java.lang.String[][][][]
"。
package points;
class Point { int x, y; }
class PointVec { Point[] vec; }
类型Point
的完全限定名称为"points.Point
";类型PointVec
的完全限定名称为"points.PointVec
";类PointVec
的字段vec
的类型的完全限定名称为"points.Point[]
"。
示例 6.7-2. 完全限定名称与规范名称
package p;
class O1 { class I {} }
class O2 extends O1 {}
p.O1.I
和p.O2.I
都是表示成员类I
的完全限定名称,但只有p.O1.I
是其规范名称。
第7章:包和模块
7.3 编译单元
CompilationUnit是Java程序的语法文法(2.1)的目标符号。它由以下产生式定义:
- CompilationUnit:
- OrdinaryCompilationUnit
- UnnamedClassCompilationUnit
- ModularCompilationUnit
- OrdinaryCompilationUnit:
- [PackageDeclaration] {ImportDeclaration} {TopLevelClassOrInterfaceDeclaration}
- UnnamedClassCompilationUnit:
- {ImportDeclaration} {ClassMemberDeclarationNoMethod} MethodDeclaration {ClassMemberDeclaration}
- ClassMemberDeclarationNoMethod:
- FieldDeclaration
- ClassDeclaration
- InterfaceDeclaration
-
;
- ModularCompilationUnit:
- {ImportDeclaration} ModuleDeclaration
普通编译单元由三部分组成,每部分都是可选的:
package
声明( 7.4),给出编译单元所属的包的完全限定名称( 6.7)。package
声明的编译单元是未命名包的一部分( 7.4.2)。-
import
声明(7.5),允许引用其他包的类和接口,以及类和接口的static
成员,使用它们的简单名称。 7.6)。
一个未命名的类编译单元包括:
-
零个或多个
import
声明,允许引用其他包中的类和接口,以及类和接口的静态成员,使用它们的简单名称。 -
隐式声明的顶层类的成员声明(8.2),其中至少有一个是方法声明(8.4)。
这意味着以下编译单元明确是普通编译单元:
import p.*; class Test { ... }
而以下编译单元明确是未命名的类编译单元:
import p.*; static void main(){ ... } class Test { ... }
未命名的类编译单元隐式声明一个满足以下属性的类:
-
它始终是一个顶层类(7.6)。
-
它始终是一个未命名类(没有规范的或完全限定的名称(6.7))。
-
它永远不是
abstract
的(8.1.1.1)。 -
它始终是
final
的(8.1.1.2)。 -
它始终是未命名包的成员(7.4.2),并具有包访问权限。
-
它的直接超类类型始终是
Object
(8.1.4)。 -
它从不具有任何直接超接口类型(8.1.5)。
-
类的主体包含来自未命名类编译单元的每个ClassMemberDeclaration(这些是字段(8.3)、方法(8.4)、成员类(8.5)和成员接口(9.1.1.3)的声明)。未命名类编译单元不可能声明实例初始化程序(8.6)、静态初始化程序(8.7)或构造函数(8.8)。
-
它具有隐式声明的默认构造函数(8.8.9)。
该类的所有成员,包括任何隐式声明的成员,都受到类中成员声明的通常规则的约束。
如果该类没有声明候选的main
方法,则编译时会出现错误(12.1.4)。
请注意,未命名包可以有多个未命名类作为成员。
一个模块化编译单元包括一个module
声明(7.7),可选择地在其前面有import
声明。这些import
声明允许在module
声明内使用其简单名称引用来自此模块和其他模块的包中的类和接口,以及类和接口的静态成员。
每个编译单元隐式导入预定义包java.lang
中声明的每个public
类或接口,就好像在每个编译单元的开头出现了声明import java.lang.*;
一样。因此,所有这些类和接口的名称在每个编译单元中都可以作为简单名称使用。
主机系统确定哪些编译单元是可观察的,除了预定义包java
及其子包lang
和io
中的编译单元始终是可观察的。
每个可观察的编译单元可能与一个模块关联,如下所示:
-
主机系统可以确定可观察的普通编译单元与主机系统选择的模块相关联,除了(i)预定义包
java
及其子包lang
和io
中的普通编译单元始终与java.base
模块相关联,以及(ii)任何未命名包中的普通编译单元,其关联的模块如7.4.2中指定的那样。 -
主机系统必须确定可观察的模块化编译单元与模块声明的模块相关联。
在编译与模块M相关联的模块化和普通编译单元时,主机系统必须遵守M声明中指定的依赖关系。具体来说,主机系统必须将通常可观察的普通编译单元限制为仅对M可见的那些。对M可见的普通编译单元是与被M读取的模块相关联的可观察的普通编译单元。被M读取的模块由解析的结果确定,如java.lang.module
包规范中所述,其中M是唯一的根模块。主机系统必须执行解析以确定被M读取的模块;如果解析因java.lang.module
包规范中描述的任何原因而失败,则编译时会出现错误。
可读性关系是自反的,因此M读取自身,因此与M相关联的所有模块化和普通编译单元对M可见。
被M读取的模块驱动着对M唯一可见的包(7.4.3)的选择,进而驱动了作用域中的顶层包以及模块化和普通编译单元中的包名称的含义(6.3,6.5.3,6.5.5)。
上述规则确保模块化编译单元中注解中使用的包和类型名称(特别是应用于模块声明的注解)被解释为如果它们出现在与模块相关联的普通编译单元中一样。
在不同普通编译单元中声明的类和接口可以相互引用,循环引用。Java编译器必须安排同时编译所有这些类和接口。
第8章:类
类声明定义了一个新类并描述了它的实现方式(8.1)。
一个顶层类(7.6)是直接在编译单元中声明的类。
一个嵌套类是其声明发生在另一个类或接口声明的主体内的任何类。嵌套类可以是成员类(8.5,9.5)、局部类(14.3)或匿名类(15.9.5)。
某些类型的嵌套类是内部类(8.1.3),它是一个可以引用封闭类实例、局部变量和类型变量的类。
一个枚举类(8.9)是使用简化语法声明的类,定义了一组命名的类实例。
一个记录类(8.10)是使用简化语法声明的类,定义了一组简单值的聚合。
对于非常小的程序和非正式开发,顶层类可以是未命名的,即没有规范的或完全限定的名称(6.7)。未命名类从不会被显式声明,而是通过未命名类编译单元隐式声明(7.3)。未命名的顶层类可以是程序的初始类(12.1.4),但不能从任何源代码中,包括其自身,引用。
类可以声明为public
(8.1.1),以便可以从其模块的任何包中引用它,可能还可以从其他模块的代码中引用。
abstract
(
8.1.1.1),如果它没有完全实现,则必须声明为
abstract
;这样的类不能被实例化,但可以被子类扩展。类的可扩展程度可以被显式控制(
8.1.1.2):它可以声明为
sealed
以限制其子类,或者可以声明为
final
以确保没有子类。除了
Object
之外的每个类都是现有类的扩展(即子类),并且可以实现接口(
一个类可以是泛型(8.1.2),也就是说,它的声明可以引入类型变量,这些类型变量在类的不同实例之间有所不同。
类声明可以用注解(9.7)进行修饰,就像任何其他类型的声明一样。
类的主体声明了成员(字段、方法、类和接口)、实例和静态初始化程序以及构造函数(8.1.7)。成员(8.2)的作用域(6.3)是其所属的类声明的整个主体。字段、方法、成员类、成员接口和构造函数的声明可以包括访问修饰符public
、protected
或private
(6.6)。类的成员包括已声明的和继承的成员(8.2)。新声明的字段可以隐藏在超类或超接口中声明的字段。新声明的成员类和成员接口可以隐藏在超类或超接口中声明的成员类和成员接口。新声明的方法可以隐藏、实现或覆盖在超类或超接口中声明的方法。
字段声明(8.3)描述类变量,它们只被实例化一次,以及实例变量,它们为类的每个实例新实例化。字段可以声明为final
(8.3.1.2),在这种情况下,它只能被赋值一次。任何字段声明都可以包括初始化程序。
成员类声明(8.5)描述作为周围类成员的嵌套类。成员类可以是static
的,这样它们就无法访问周围类的实例变量;或者它们可以是内部类。
成员接口声明(8.5)描述作为周围类成员的嵌套接口。
方法声明(8.4)描述可以通过方法调用表达式(15.12)调用的代码。类方法是相对于类调用的;实例方法是相对于某个特定对象调用的,该对象是类的实例。声明未指示如何实现的方法必须声明为abstract
。方法可以声明为final
(8.4.3.3),在这种情况下,它不能被隐藏或覆盖。方法可以由平台相关的native
代码实现(8.4.3.4)。一个synchronized
方法(8.4.3.6)在执行其主体之前自动锁定对象,并在返回时自动解锁对象,就像使用synchronized
语句(14.19)一样,从而允许其活动与其他线程的活动同步(17)。
方法名称可以被重载(8.4.9)。
实例初始化程序(8.6)是可执行代码块,可用于在创建实例时帮助初始化实例(15.9)。
静态初始化程序(8.7)是可执行代码块,可用于帮助初始化类。
构造函数(8.8)类似于方法,但不能直接通过方法调用调用;它们用于初始化新的类实例。与方法一样,它们可以被重载(8.8.8)。
8.1 类声明
一个类声明指定一个类。
有三种类声明: 普通类声明、枚举声明(8.9)和记录声明(8.10)。
- ClassDeclaration:
- NormalClassDeclaration
- EnumDeclaration
- RecordDeclaration
- NormalClassDeclaration:
-
{ClassModifier}
class
TypeIdentifier [TypeParameters]
[ClassExtends] [ClassImplements] [ClassPermits] ClassBody
一个类也可以通过未命名的类编译单元(7.3)、类实例创建表达式(15.9.5)和以类主体结尾的枚举常量来隐式声明。
类声明中的TypeIdentifier指定了类的名称。
如果一个类具有与其封闭类或接口中的任何简单名称相同的名称,则会导致编译时错误。
第9章:接口
接口声明定义了一个新的接口,可以由一个或多个类实现。程序可以使用接口为否则无关的类提供一个共同的超类型,并且使得相关类不必共享一个abstract
超类。
接口没有实例变量,并且通常声明一个或多个abstract
方法;否则无关的类可以通过为其abstract
方法提供实现来实现一个接口。接口不能直接实例化。
一个顶层接口(7.6)是直接在编译单元中声明的接口。
一个嵌套接口是在另一个类或接口声明的主体中发生的任何接口。嵌套接口可以是成员接口(8.5,9.5)或局部接口(14.3)。
一个注解接口(9.6)是使用不同语法声明的接口,旨在由注解的反射表示实现(9.7)。
本章讨论了所有接口的共同语义。特定于特定接口类型的细节在专门讨论这些构造的章节中讨论。
接口可以声明为直接扩展一个或多个其他接口,这意味着它继承了所有成员类和接口、实例方法和static
字段的接口,除了它可能覆盖或隐藏的任何成员。
一个类可以声明为直接实现一个或多个接口(8.1.5),这意味着类的任何实例都实现了接口指定的所有abstract
方法。一个类必然实现其直接超类和直接超接口所做的所有接口。这种(多重)接口继承允许对象支持(多个)共同行为,而无需共享一个超类。
与类不同,接口不能声明为final
。但是,接口可以声明为sealed
(9.1.1.4)以限制其子类和子接口。
一个声明类型为接口类型的变量可以将其值作为实现指定接口的类的任何实例的引用。类恰好实现接口的所有abstract
方法是不够的;类或其超类必须实际声明为实现接口,否则该类不被视为实现接口。
请注意,与类不同,不可能声明,甚至是隐式地声明一个未命名的顶层接口(7.3)。
第12章:执行
本章指定了程序执行过程中发生的活动。它围绕Java虚拟机的生命周期以及构成程序的类、接口和对象展开。
Java虚拟机通过加载指定的类或接口启动,然后调用
thea方法main
在这个指定的类或接口中。第12.1节概述了执行main
所涉及的加载、链接和初始化步骤,作为本章概念的介绍。进一步的章节详细说明了加载(12.2)、链接(12.3)和初始化(12.4)的细节。
本章继续详细说明了创建新类实例的过程(12.5);以及类实例的最终化(12.6)。最后描述了类的卸载(12.7)以及程序退出时的过程(12.8)。
12.1 Java虚拟机启动
Java虚拟机通过调用某个指定类或接口的方法main
来开始执行。如果这个main
方法有一个形式参数,它将被传递一个字符串数组作为参数。
Java虚拟机启动的确切语义在《Java虚拟机规范,Java SE 21版》的第5章中给出。这里我们从Java编程语言的角度概述了这个过程。
指定Java虚拟机初始类或接口的方式超出了本规范的范围,但在使用命令行的主机环境中,通常会将完全限定的初始类或接口名称作为命令行参数指定,并将后续的命令行参数用作要提供给方法main
的字符串。如果原始编译单元是一个未命名的类编译单元,那么通常会使用包含编译单元的文件名来指定初始类或接口的名称。
例如,在UNIX实现中,以下命令行:
java Test reboot Bob Dot Enzo
通常会通过调用类
Test
的方法main
(一个未命名包中的类),将包含四个字符串“reboot
”、“Bob
”、“Dot
”和“Enzo
”的参数数组传递给它。
而如果文件
HelloWorld.java
包含以下未命名类编译单元:void main() { System.out.println("Hello, World!"); }
经过编译后,以下命令行:
java HelloWorld
通常会通过调用隐式声明的未命名类(7.3)的
main
方法来启动Java虚拟机,输出:Hello, World!
现在我们概述Java虚拟机执行初始类或接口的步骤,作为后续章节中进一步描述的加载、链接和初始化过程的示例。
12.1.1 加载初始类或接口
尝试执行初始类或接口的方法main
时,发现它尚未加载 - 也就是说,Java虚拟机当前没有这个类或接口的二进制表示。然后Java虚拟机使用类加载器尝试找到这样的二进制表示。如果这个过程失败,那么将抛出错误。这个加载过程在12.2中进一步描述。
12.1.2 链接初始类或接口:验证、准备、(可选)解析
在加载类或接口后,必须在调用方法main
之前对其进行初始化。而且,像所有类和接口一样,必须在初始化之前对其进行链接。链接包括验证、准备和(可选)解析。链接在12.3中进一步描述。
验证检查加载的类或接口的表示形式是否格式良好,具有适当的符号表。验证还检查实现类或接口的代码是否符合Java编程语言和Java虚拟机的语义要求。如果在验证过程中检测到问题,则会抛出错误。验证在12.3.1中进一步描述。
准备涉及分配静态存储和Java虚拟机实现内部使用的任何数据结构,例如方法表。准备在12.3.2中进一步描述。
解析是检查从类或接口到其他类和接口的符号引用的过程,通过加载提及的其他类和接口并检查引用是否正确来完成。
解析步骤在初始链接时是可选的。实现可以非常早地解析从正在链接的类或接口到其他类和接口的符号引用,甚至可以解析进一步被引用的类和接口的所有符号引用,递归地。 (这种解析可能导致这些进一步加载和链接步骤中的错误。)这种实现选择代表了一种极端情况,类似于多年来在C语言的简单实现中所做的“静态”链接。 (在这些实现中,编译后的程序通常表示为包含程序的完全链接版本的“a.out”文件,包括对程序使用的库例程的完全解析链接。这些库例程的副本包含在“a.out”文件中。)
相反,实现可以选择仅在被主动使用时解析符号引用;对所有符号引用一致使用这种策略将代表“最懒惰”的解析形式。在这种情况下,如果初始类或接口引用另一个类的符号引用,则这些引用可能会逐个解析,因为它们被使用,或者在程序执行期间从未使用这些引用,则可能根本不解析。
解析何时执行的唯一要求是,在解析期间检测到的任何错误必须在程序中采取某些操作可能直接或间接需要与涉及错误的类或接口进行链接的点抛出。使用上述“静态”示例实现选择,如果涉及初始类或接口或任何进一步递归引用的类和接口的类或接口,则在程序执行之前可能会发生加载和链接错误。在实现了“最懒惰”解析的系统中,只有在主动使用不正确的符号引用时才会抛出这些错误。
解析过程在12.3.3中进一步描述。
12.1.3 初始化初始类或接口:执行初始化器
在我们持续的示例中,Java虚拟机仍在尝试执行初始类或接口的方法main
。只有在类已经初始化后才允许这样做。
初始化包括执行初始类或接口的任何类变量初始化器和静态初始化器,按文本顺序执行。但在初始化之前,必须初始化其直接超类,以及其直接超类的直接超类,依此类推。在最简单的情况下,初始类或接口的隐式直接超类是Object
;如果类Object
尚未初始化,则必须在初始化初始类或接口之前对其进行初始化。类Object
没有超类,因此递归在此终止。
如果初始类或接口有另一个类Super
作为其超类,则在初始化初始类或接口之前必须初始化Super
。如果尚未加载、验证和准备Super
,则可能还涉及解析Super
的符号引用等递归操作。
因此,初始化可能导致加载、链接和初始化错误,包括涉及其他类和接口的错误。
初始化过程在12.4中进一步描述。
12.1.4 调用一个main
方法
最终,在完成初始类或接口的初始化后(在此期间可能发生其他相关的加载、链接和初始化),初始类或接口的main
方法将被调用。
如果初始类或接口的方法命名为main
,并且满足以下条件之一,则为候选方法:
-
它是一个
static
方法,在初始类或接口中声明,返回类型为void
,具有public
、protected
或包访问权限,并且没有形式参数或者只有一个形式参数,其声明类型为String
数组。请注意,这样的
static
方法不能从初始类或接口的超类继承而来。 -
它是一个实例方法,在初始类或接口中声明或继承,返回类型为
void
,具有public
、protected
或包访问权限,并且没有形式参数或者只有一个形式参数,其声明类型为String
数组;并且,此外,初始类或接口不是内部类。
请注意,候选
main
方法可能具有throws
子句(8.4.6)。
main
方法的允许签名在Java SE 21中得到了显著扩展。在此之前,main
方法签名的唯一变化可能是单个形式参数的类型为String[]
还是String...
。在Java SE 21及以上版本中,main
可以有十二种可能的签名之一:六种static
和六种非static
。如果在单个形式参数的类型中区分String[]
和String...
,则此数字增加到18。请注意,如果初始类或接口的成员中包含多个候选
main
方法,这不会导致编译时错误。类或接口中存在
main
方法可能并不明显,因为非static
main
方法可能会被继承。例如,接口中的默认方法是实例方法(9.4),因此在被实现该接口的类继承时可能成为候选方法。开发工具鼓励突出显示类或接口是否具有可作为程序起点的main
方法。在Java SE 21中进行了行为更改,继承的
static
main
方法不再被视为候选方法。任何现有的初始类或接口,其唯一的main
方法既是static
又是继承的,都需要进行重构以继续作为程序的起点。
通过以下规则调用初始类或接口的main
方法:
-
如果存在带有形式参数的
static
候选方法,则调用此方法,并传递参数数组(12.1)。 -
否则,如果存在没有形式参数的
static
候选方法,则调用此方法。 -
否则,如果存在带有形式参数的实例候选方法,则在使用没有形式参数且具有
public
、protected
或包访问权限的构造函数创建的初始类实例上调用此方法,并传递参数数组。 -
否则,如果存在没有形式参数的实例候选方法,则在使用没有形式参数且具有
public
、protected
或包访问权限的构造函数创建的初始类实例上调用此方法。
如果没有候选方法可供调用,或者在调用实例候选方法时初始类中没有合适的构造函数,实现的行为超出了本规范的范围。
第13章:二进制兼容性
13.1 二进制的形式
程序必须编译成由Java虚拟机规范,Java SE 20版指定的class
文件格式,或者编译成可以由Java编程语言编写的类加载器映射到该格式的表示形式。
与类或接口声明对应的class
文件必须具有某些属性。其中一些属性专门选择以支持保留二进制兼容性的源代码转换。所需的属性包括:
-
类或接口必须以其二进制名称命名,必须符合以下约束:
-
命名的顶层类或接口(7.6)的二进制名称是其规范名称(6.7)。未命名的顶层类(7.3)的二进制名称是任何有效的标识符(3.8)。
在Java SE平台的简单实现中,编译单元存储在文件中,未命名的顶层类的二进制名称通常是包含未命名顶层类编译单元的文件名(7.3),减去任何扩展名(如
.java
或.jav
)。 -
局部类或接口(14.3)的二进制名称由其直接封闭类或接口的二进制名称组成,后跟
$
,后跟非空数字序列,后跟局部类的简单名称。 -
匿名类(15.9.5)的二进制名称由其直接封闭类或接口的二进制名称组成,后跟
$
,后跟非空数字序列。 -
由泛型类或接口声明的类型变量(8.1.2,9.1.2)的二进制名称是其直接封闭类或接口的二进制名称,后跟
$
,后跟类型变量的简单名称。 -
由泛型方法声明的类型变量(8.4.4)的二进制名称是声明该方法的类或接口的二进制名称,后跟
$
,后跟方法的描述符(JVMS §4.3.3),后跟$
,后跟类型变量的简单名称。 -
由泛型构造函数声明的类型变量(8.8.4)的二进制名称是声明构造函数的类的二进制名称,后跟构造函数的描述符(JVMS §4.3.3),后跟
$
,后跟类型变量的简单名称。
-
-
对另一个类或接口的引用必须是符号的,使用类或接口的二进制名称。
-
对常量变量的字段的引用(4.12.4)必须在编译时解析为常量变量初始化程序所表示的值V。
如果这样的字段是
static
,则在二进制文件中的代码中不应存在对字段的引用,包括声明该字段的类或接口。这样的字段必须始终看起来已被初始化(12.4.2);字段的默认初始值(如果与V不同)绝不能被观察到。如果这样的字段是非
static
,则在二进制文件中的代码中不应存在对字段的引用,除了包含该字段的类。(它将是一个类而不是接口,因为接口只有static
字段。)该类应该有代码在实例创建期间将字段的值设置为V(12.5)。 -
给定一个在类C中表示字段访问的合法表达式,引用一个名为f的字段,该字段不是常量变量并且在(可能不同的)类或接口D中声明,我们定义字段引用的限定类或接口如下:
-
如果表达式由一个简单名称引用,那么如果f是当前类或接口C的成员,则让Q为C。否则,让Q为f是成员的最内层词法封闭类或接口声明。在任何情况下,Q是引用的限定类或接口。
-
如果引用的形式为TypeName
.
f,其中TypeName表示一个类或接口,则TypeName表示的类或接口是引用的限定类或接口。 -
如果表达式的形式为ExpressionName
.
f或Primary.
f,则: -
如果表达式的形式为
super.
f,则C的超类是引用的限定类或接口。 -
如果表达式的形式为TypeName
.super.
f,则TypeName表示的类的超类是引用的限定类或接口;如果TypeName表示一个接口X,则X是引用的限定类或接口。
对f的引用必须编译为对引用的限定类或接口的符号引用,加上字段的简单名称f。
引用还必须包括对字段声明类型的擦除的符号引用,以便验证器可以检查类型是否符合预期。
-
-
在类或接口C中给定一个方法调用表达式或方法引用表达式,引用一个名为m的方法,该方法在(可能不同的)类或接口D中声明(或隐式声明(9.2)),我们定义方法调用的方法调用的限定类或接口如下:
-
如果D是
Object
,则方法调用的限定类或接口是Object
。 -
否则:
-
如果方法由一个简单名称引用,那么如果m是当前类或接口C的成员,则让Q为C;否则,让Q为m是成员的最内层词法封闭类或接口声明。在任何情况下,Q是方法调用的限定类或接口。
-
如果表达式的形式为TypeName
.
m或ReferenceType::
m,则TypeName表示的类或接口,或ReferenceType的擦除,是方法调用的限定类或接口。 -
如果表达式的形式为ExpressionName
.
m或Primary.
m或ExpressionName::
m或Primary::
m,则:-
如果ExpressionName或Primary的编译时类型是交集类型V1
&
...&
Vn,则方法调用的限定类或接口是V1的擦除。 -
否则,ExpressionName或Primary的编译时类型的擦除是方法调用的限定类或接口。
-
-
如果表达式的形式为
super.
m或super::
m,则C的超类是方法调用的限定类或接口。 -
如果表达式的形式为TypeName
.super.
m或TypeName.super::
m,则如果TypeName表示一个类X,则X的超类是方法调用的限定类或接口;如果TypeName表示一个接口X,则X是方法调用的限定类或接口。
-
对方法的引用必须在编译时解析为对方法调用的限定类或接口的符号引用,加上方法的声明签名的擦除(8.4.2)的符号引用。方法的签名必须包括由15.12.3确定的以下所有内容:
-
方法的简单名称
-
方法的参数数量
-
每个参数的类型的符号引用
对方法的引用还必须包括对所表示方法的返回类型的擦除的符号引用,或者指示所表示方法声明为
void
且不返回值。 -
-
给定一个类实例创建表达式(15.9)或一个显式构造函数调用语句(8.8.7.1)或形式为 ClassType
::
new
的方法引用表达式(15.13)在类或接口 C 中,引用一个在(可能不同的)类或接口 D 中声明的构造函数 m,我们定义构造函数调用的 限定类 如下:-
如果表达式形式为
new
D(...)
或 ExpressionName.new
D(...)
或 Primary.new
D(...)
或 D::
new
,那么构造函数调用的限定类为 D。 -
如果表达式形式为
new
D(...){...}
或 ExpressionName.new
D(...){...}
或 Primary.new
D(...){...}
,那么构造函数调用的限定类为表达式声明的匿名类。 -
如果表达式形式为
super(...)
或 ExpressionName.super(...)
或 Primary.super(...)
,那么构造函数调用的限定类为 C 的直接超类。 -
如果表达式形式为
this(...)
,那么构造函数调用的限定类为 C。
对构造函数的引用必须在编译时解析为构造函数调用的限定类的符号引用,加上构造函数的声明签名(8.8.2)。构造函数的签名必须包括:
-
构造函数的参数数量
-
每个形式参数类型的符号引用
-
类或接口的二进制表示还必须包含以下所有内容:
-
如果它是一个类且不是
Object
,则对该类的直接超类的符号引用。 -
对每个直接超接口的符号引用(如果有)。
-
对类或接口中声明的每个字段的规范,给出字段的简单名称和对字段类型的擦除类型的符号引用。
-
如果它是一个类,则对每个构造函数的擦除签名,如上所述。
-
对类或接口中声明的每个方法(对于接口,不包括其隐式声明的方法(9.2))的擦除签名和返回类型,如上所述。
-
实现类或接口所需的代码:
-
每个类或接口必须包含足够的信息以恢复其规范名称(6.7)。
-
每个成员类或接口必须包含足够的信息以恢复其源级访问修饰符(6.6)。
-
每个嵌套类或接口必须具有对其直接封闭类或接口的符号引用(8.1.3)。
-
每个类或接口必须包含对其所有成员类和接口的符号引用(8.5,9.5),以及对其主体内声明的所有其他嵌套类和接口的符号引用。
-
Java编译器生成的构造必须标记为合成的,如果它不对应于源代码中明确或隐式声明的构造
,除非生成的构造是类初始化方法(JVMS §2.9)。有两个例外: -
Java编译器生成的构造必须标记为强制的,如果它对应于源代码中隐式声明的形式参数(8.8.1,8.8.9,8.9.3,15.9.5.1)。
以下形式参数在源代码中隐式声明:
参考以下构造在源代码中隐式声明,但未标记为强制,因为只有形式参数和模块可以在
class
文件中标记为强制(JVMS §4.7.24,JVMS §4.7.25):
与模块声明对应的class
文件必须具有二进制名称为module-info
且没有超类、超接口、字段和方法的类的class
文件的属性。此外,模块的二进制表示必须包含以下所有内容:
-
模块名称的规范,给出作为
module
后指示的名称的符号引用。此外,规范必须包括模块是正常的还是开放的(7.7)。 -
由
requires
指令表示的每个依赖的规范,给出指令指示的模块名称的符号引用(7.7.1)。此外,规范必须包括依赖是否是transitive
以及依赖是否是static
。 -
由
exports
或opens
指令表示的每个包的规范,给出指令指示的包名称的符号引用(7.7.2)。此外,如果指令有资格,规范必须给出指令的to
子句指示的模块名称的符号引用。 -
由
uses
指令表示的每个服务的规范,给出指令指示的类或接口名称的符号引用(7.7.3)。 -
由
provides
指令表示的服务提供者的规范,给出指令的with
子句指示的类和接口名称的符号引用(7.7.4)。此外,规范必须给出指令指示的类或接口作为服务的名称的符号引用。
以下部分讨论可以对类和接口声明进行的更改,而不会破坏与现有二进制文件的兼容性。根据上述翻译要求,Java虚拟机及其class
文件格式支持这些更改。任何其他有效的二进制格式,例如通过类加载器在上述要求下映射回class
文件的压缩或加密表示,必然也支持这些更改。