Unnamed patterns and variables (Preview)

Changes to the Java® Language Specification • Version 21.0.1+12-LTS-29

本文档描述了对Java语言规范的更改,以支持未命名模式和变量,这是Java SE 21的预览功能。有关该功能的概述,请参阅JEP:443

更改是针对JLS的现有部分描述的。新文本显示为这样,删除的文本显示为 这样。必要时,解释和讨论将放在灰色框中。

变更日志:

2023-03-22:初始规范草案。

第3章:词法结构

3.8 标识符

标识符是一个无限长度的Java字母Java数字序列,其中第一个必须是一个Java字母

标识符:
标识符字符 但不能是 关键字 布尔文字 空文字
标识符字符:
Java字母 {Java字母或数字}
Java字母:
任何Unicode字符,它是一个“Java字母”
Java字母或数字:
任何Unicode字符,它是一个“Java字母或数字”

“Java字母”是一个方法Character.isJavaIdentifierStart(int)返回true的字符。

“Java字母或数字”是一个方法Character.isJavaIdentifierPart(int)返回true的字符。

“Java字母”包括大写和小写ASCII拉丁字母A-Z\u0041-\u005a),和a-z\u0061-\u007a),以及出于历史原因,ASCII美元符号($,或\u0024)和下划线(_,或\u005f)。美元符号应仅在机械生成的源代码中使用,或者在传统系统上访问预先存在的名称时偶尔使用。下划线可以用于由两个或更多字符组成的标识符,但由于是关键字,不能用作单字符标识符3.9

“Java数字”包括ASCII数字0-9\u0030-\u0039)。

字母和数字可以来自整个Unicode字符集,该字符集支持当今世界上大多数使用的书写脚本,包括用于中文、日文和韩文的大型集合。这使程序员可以在其程序中使用用其母语编写的标识符。

仅当忽略可忽略字符后,标识符的每个字母或数字具有相同的Unicode字符时,两个标识符才相同。可忽略字符是一个方法Character.isIdentifierIgnorable(int)返回true的字符。外观相同的标识符可能是不同的。

例如,由单个字母组成的标识符 LATIN CAPITAL LETTER A(A\u0041),LATIN SMALL LETTER A(a\u0061),GREEK CAPITAL LETTER ALPHA(A\u0391),CYRILLIC SMALL LETTER A(a\u0430)和MATHEMATICAL BOLD ITALIC SMALL A(a\ud835\udc82)都是不同的。

Unicode组合字符与其规范等效的分解字符不同。例如,拉丁大写字母A ACUTE(Á\u00c1)与紧随其后的非间距ACUTE(´\u0301)在标识符中是不同的。请参阅Unicode标准,第3.11节“规范化形式”。

标识符的示例包括:

标识符永远不会与保留关键字(3.9)、布尔文字(3.10.3)或空文字(3.10.8)具有相同的拼写(Unicode字符序列),这是由于标记化规则(3.5)的规定。但是,标识符可能与上下文关键字具有相同的拼写,因为将输入字符序列标记化为标识符或上下文关键字取决于序列在程序中出现的位置。

为了便于识别上下文关键字,语法语法(2.3)有时通过定义接受仅标识符的子集来禁止某些标识符。子集如下:

类型标识符:
标识符 但不能是 permitsrecordsealedvaryield
未限定方法标识符:
标识符 但不能是 yield

类型标识符用于类、接口和类型参数的声明(8.19.14.4),以及引用类型时(6.5)。例如,类的名称必须是一个类型标识符,因此不允许声明名为permitsrecordsealedvaryield的类。

未限定方法标识符用于方法调用表达式通过其简单名称引用方法时(6.5.7.1)。由于术语yield被排除在未限定方法标识符之外,因此必须对名为yield的方法的任何调用进行限定,从而区分调用和yield语句(14.21)。

3.9 关键字

以下更改假定由JEP 440(记录模式)和JEP 441(switch的模式匹配)导致的JLS更改已应用(JLS:JEP440+441)。

由ASCII字符组成的51个字符序列被保留用作关键字,不能用作标识符(3.8)。另外,由ASCII字符组成的另外16个字符序列,根据它们出现的上下文,可以被解释为关键字或其他标记。

关键字:
保留关键字
上下文关键字
保留关键字:
(其中之一)
abstract continue for new switch
assert default if package synchronized
boolean do goto private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const float native super while
_(下划线)
上下文关键字:
(其中之一)
exports permits sealed var
module provides to when
non-sealed record transitive with
open requires uses yield
opens

关键字constgoto被保留,即使它们目前未被使用。这可以让Java编译器在程序中错误地出现这些C++关键字时产生更好的错误消息。

关键字strictfp已过时,不应在新代码中使用。

关键字_(下划线)被保留,以备将来在参数声明中使用。

关键字_(下划线)可以在声明中代替标识符使用(6.1)。

truefalse不是关键字,而是布尔文字(3.10.3)。

null不是关键字,而是空文字(3.10.8)。

在将输入字符减少为输入元素时(3.5),如果概念上匹配上下文关键字的输入字符序列仅在以下两个条件都满足时才减少为上下文关键字:

  1. 该序列被识别为语法文法适当上下文中的终结符(2.3),如下所示:

    • 对于moduleopen,当被识别为ModuleDeclaration中的终结符时(7.7)。

    • 对于exportsopensprovidesrequirestouseswith,当被识别为ModuleDirective中的终结符时。

    • 对于transitive,当被识别为RequiresModifier中的终结符时。

      例如,识别序列requires transitive ;并不使用RequiresModifier,因此术语transitive在此被减少为标识符而不是上下文关键字。

    • 对于var,当被识别为LocalVariableType14.4)或LambdaParameterType15.27.1)中的终结符时。

      在其他上下文中,尝试将var用作标识符将导致错误,因为var不是TypeIdentifier3.8)。

    • 对于yield,当被识别为YieldStatement中的终结符时(14.21)。

      在其他上下文中,尝试将yield用作标识符将导致错误,因为yield既不是TypeIdentifier也不是UnqualifiedMethodIdentifier

    • 对于record,当被识别为RecordDeclaration中的终结符时(8.10)。

    • 对于non-sealedpermitssealed,当被识别为NormalClassDeclaration8.1)或NormalInterfaceDeclaration9.1)中的终结符时。

    • 对于when,当被识别为Guard中的终结符时(14.11.1)。

  2. 该序列的前一个字符和后一个字符不是JavaLetterOrDigit匹配的输入字符。

通常,在源代码中意外省略空格将导致一系列输入字符被标记为标识符,这是由于“最长可能转换”规则(3.2)。例如,十二个输入字符的序列p u b l i c s t a t i c总是被标记为标识符publicstatic,而不是保留关键字publicstatic。如果意图是两个标记,它们必须用空格或注释分隔。

上述规则与“最长可能转换”规则共同作用,以在可能出现上下文关键字的情况下产生直观的结果。例如,十一个输入字符的序列v a r f i l e n a m e通常被标记为标识符varfilename,但在局部变量声明中,前三个输入字符被第一个规则条件暂时识别为上下文关键字var。然而,忽略序列中缺少空格会导致下一个八个输入字符被识别为标识符filename。 (这意味着序列在不同上下文中经历不同的标记化过程:在大多数上下文中是标识符,但在局部变量声明中是上下文关键字和标识符。)因此,第二个条件阻止了对上下文关键字var的识别,理由是紧随其后的输入字符fJavaLetterOrDigit。因此,在局部变量声明中,序列v a r f i l e n a m e被标记为标识符varfilename

作为对上下文关键字的仔细识别的另一个例子,请考虑十五个输入字符的序列n o n - s e a l e d c l a s s。该序列通常被转换为三个标记 - 标识符non、运算符-和标识符sealedclass - 但在普通类声明中,第一个条件成立,前十个输入字符被暂时识别为上下文关键字non-sealed。为避免将序列转换为两个关键字标记(non-sealedclass)而不是三个非关键字标记,并且避免因省略class前的空格而奖励程序员,第二个条件阻止了上下文关键字的识别。因此,在类声明中,序列n o n - s e a l e d c l a s s被标记为三个标记。

在上述规则中,第一个条件取决于语法文法的细节,但Java编程语言的编译器可以在不完全解析输入程序的情况下实现该规则。例如,可以使用启发式方法来跟踪标记化器的上下文状态,只要该启发式方法保证上下文关键字的有效使用被标记为关键字,标识符的有效使用被标记为标识符。或者,编译器可以始终将上下文关键字标记为标识符,留待后续阶段识别这些标识符的特殊用途。

第6章:名称

6.1 声明

一个声明将一个实体引入程序,并包括一个标识符(3.8),该标识符可用于名称中引用此实体。当引入的实体是类、接口或类型参数时,标识符受限以避免某些上下文关键字。

声明的实体是以下之一:

一个声明将一个实体引入程序,以下之一:

构造函数(8.88.10.4)也是通过声明引入的,但使用声明它们所在类的名称,而不是引入新名称。

声明通常包括一个标识符(3.8),可用于名称中引用已声明的实体。当引入的实体是类、接口或类型参数时,标识符受限以避免某些上下文关键字。

如果声明不包括标识符,而是包括保留关键字_(下划线),则该实体不能通过名称引用。以下种类的实体可以使用下划线声明:

使用下划线声明的局部变量、异常参数或lambda参数称为未命名局部变量、未命名异常参数或未命名lambda参数。

泛型类或接口的声明(class C<T> ...interface C<T> ...)引入了一个名为C的类和一组类型:原始类型C,参数化类型C<Foo>,参数化类型C<Bar>等。

当在不重要的泛型上下文中出现对C的引用时,如下所示标识为非泛型上下文之一,对C的引用表示类或接口C。在其他上下文中,对C的引用表示由C引入的类型或类型的一部分。

15个非泛型上下文如下:

  1. 在模块声明的usesprovides指令中(7.7.1

  2. 在单类型导入声明中(7.5.1

  3. 在单静态导入声明中.的左侧(7.5.3

  4. 在静态导入声明中.的左侧(7.5.4

  5. sealed类或接口声明的permits子句中(8.1.69.1.4)。

  6. 在构造函数声明中(的左侧(8.8

  7. 在注解中@符号后(9.7

  8. 在类字面量中.class的左侧(15.8.2

  9. 在限定this表达式中.this的左侧(15.8.4

  10. 在限定超类字段访问表达式中.super的左侧(15.11.2

  11. 在限定方法调用表达式中.标识符.super.标识符的左侧(15.12

  12. 在方法引用表达式中.super::的左侧(15.13

  13. 在后缀表达式或try-with-resources语句中的限定表达式名称中(15.14.114.20.3

  14. 在方法或构造函数的throws子句中(8.4.68.8.59.4

  15. 在异常参数声明中(14.20

前十二个非泛型上下文对应于TypeName中的前十二个句法上下文[6.5.1]。第十三个非泛型上下文是指一个限定的ExpressionName,例如C.x可能包括一个TypeNameC来表示静态成员访问。在这十三个上下文中普遍使用TypeName是重要的:它表明这些上下文涉及对类型的非一流使用。相比之下,第十四和第十五个非泛型上下文使用ClassType,表明throwscatch子句以一流方式使用类型,与例如字段声明一致。将这两个上下文描述为非泛型的原因是异常类型不能被参数化的事实(8.1.2)。

请注意,ClassType生成允许注解,因此可以对throwscatch子句中类型的使用进行注释,而TypeName生成不允许注解,因此不可能对类型的名称进行注释,例如在单类型导入声明中。

命名约定

Java SE平台的类库尽可能使用以下约定选择的名称。这些约定有助于使代码更易读,并避免某些名称冲突。

我们建议在Java编程语言中编写的所有程序中使用这些约定。但是,如果长期以来的传统用法要求不同,就不应盲目遪从这些约定。例如,java.lang.Math类的sincos方法具有数学上的传统名称,尽管这些方法名称违反了这里建议的约定,因为它们很短并且不是动词。

包名和模块名

程序员应采取措施避免两个发布的包具有相同的名称,方法是为广泛分发的包选择唯一包名。这样可以轻松自动地安装和编目包。本节指定了生成这种唯一包名的建议约定。鼓励Java SE平台的实现提供自动支持,将一组包从本地和临时包名称转换为此处描述的唯一名称格式。

如果不使用唯一包名,则可能会在冲突包的创建点远处出现包名冲突。这可能会导致用户或程序员难以解决的情况。在这些情况下,可以使用ClassLoaderModuleLayer来使具有相同名称的包相互隔离,但这并不透明地对一个天真的程序进行。

您可以通过首先拥有(或隶属于拥有)互联网域名,例如oracle.com,来形成唯一包名。然后,逐个组件地反转此名称,以获得在此示例中为com.oracle,并将其用作包名的前缀,使用您的组织内开发的约定进一步管理包名。这样的约定可能会指定某些包名组件为部门、部门、项目、机器或登录名。

示例 6.1-1. 唯一包名

com.nighthacks.scrabble.dictionary
org.openjdk.compiler.source.tree
net.jcip.annotations
edu.cmu.cs.bovik.cheese
gov.whitehouse.socks.mousefinder

唯一包名的第一个组件始终以全小写ASCII字母编写,并且应为顶级域名之一,例如comedugovmilnetorg,或者根据ISO标准3166指定的国家的英文两字代码之一。

在某些情况下,互联网域名可能不是有效的包名。以下是处理这些情况的建议约定:

模块的名称应与其主要导出包的名称相对应。如果模块没有这样的包,或者出于传统原因必须具有与其导出包之一不对应的名称,则其名称仍应以作者关联的互联网域的反转形式开头。

示例 6.1-2. 唯一模块名

com.nighthacks.scrabble
org.openjdk.compiler
net.jcip.annotations

包或模块名称的第一个组件不得是标识符java。以标识符java开头的包和模块名称保留供Java SE平台的包和模块使用。

包或模块的名称并不意味着包或模块存储在互联网上的位置。例如,名为edu.cmu.cs.bovik.cheese的包不一定可以从主机cmu.educs.cmu.edubovik.cs.cmu.edu获取。生成唯一包和模块名称的建议约定仅仅是在现有广泛知名的唯一名称注册表上附加包和模块命名约定的一种方式,而不必为包和模块名称创建单独的注册表。

类和接口名称

类的名称应为描述性名词或名词短语,不要过长,采用混合大小写,每个单词的首字母大写。

示例 6.1-3. 描述性类名

`ClassLoader`
SecurityManager
`Thread`
Dictionary
BufferedInputStream

同样,接口的名称应该简短且描述性,不要过长,采用混合大小写,每个单词的首字母大写。当接口被用作抽象超类时,适用描述性名词或名词短语,例如java.io.DataInputjava.io.DataOutput接口;或者描述行为的形容词,如RunnableCloneable接口。

类型变量名称

类型变量名称应简洁(如果可能,使用单个字符),具有启发性,并且不应包含小写字母。这样可以轻松区分类型参数和普通类和接口。

容器类和接口应使用名称E表示其元素类型。映射应使用K表示其键的类型和V表示其值的类型。对于任意异常类型,应使用名称X。在没有更具体的类型来区分的情况下,我们使用T表示类型。(这在泛型方法中经常发生。)

如果有多个表示任意类型的类型参数,则应使用字母,这些字母与字母表中的 T 相邻,例如 S。或者,可以使用数字下标(例如,T1T2)来区分不同的类型变量。在这种情况下,所有具有相同前缀的变量都应带下标。

如果泛型方法出现在泛型类内部,最好避免在方法和类的类型参数中使用相同的名称,以避免混淆。嵌套泛型类也适用相同的规则。

示例 6.1-4. 传统类型变量名称

public class HashSet<E> extends AbstractSet<E> { ... }
public class HashMap<K,V> extends AbstractMap<K,V; { ... }
public class ThreadLocal<T> { ... }
public interface Functor<T, X extends Throwable> {
    T eval() throws X;
}

当类型参数不方便地归入上述类别之一时,应选择尽可能有意义的名称,限制在单个字母内。上述提到的名称(EKVXT)不应用于不属于指定类别的类型参数。

方法名称

方法名称应该是动词或动词短语,混合大小写,第一个字母小写,后续单词的第一个字母大写。以下是方法名称的一些额外特定约定:

字段名称

final 的字段名称应采用混合大小写,第一个字母小写,后续单词的第一个字母大写。请注意,设计良好的类几乎没有 publicprotected 字段,除了常量字段(static final 字段)。

字段应具有名词、名词短语或名词缩写的名称。

此约定的示例包括类 java.io.ByteArrayInputStream 的字段 bufposcount,以及类 java.io.InterruptedIOException 的字段 bytesTransferred

常量名称

接口中的常量名称应为大写字母,类的 final 变量通常也可以是一系列一个或多个单词、首字母缩写或缩写,所有字母都大写,组件之间用下划线 "_" 分隔。常量名称应具有描述性,并且不应不必要地缩写。通常,它们可以是任何适当的词性。

常量名称的示例包括类 CharacterMIN_VALUEMAX_VALUEMIN_RADIXMAX_RADIX

一组代表集合的替代值或较少频繁地,整数值中的掩码位的常量,有时可以使用常见首字母缩写作为名称前缀。

例如:

interface ProcessStates {
    int PS_RUNNING   = 0;
    int PS_SUSPENDED = 1;
}

局部变量和参数名称

局部变量和参数名称应该简短但有意义。它们通常是一系列不是单词的小写字母序列,例如:

第8章:类

8.3 字段声明

字段声明 引入。

FieldDeclaration:
{FieldModifier} UnannType VariableDeclaratorList ;
VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclaratorId [= VariableInitializer]
VariableDeclaratorId:
Identifier [Dims]
VariableDeclaratorId:
Identifier [Dims]
_
VariableInitializer:
Expression
ArrayInitializer
UnannType:
UnannPrimitiveType
UnannReferenceType
UnannPrimitiveType:
NumericType
boolean
UnannReferenceType:
UnannClassOrInterfaceType
UnannTypeVariable
UnannArrayType
UnannClassOrInterfaceType:
UnannClassType
UnannInterfaceType
UnannClassType:
TypeIdentifier [TypeArguments]
PackageName . {Annotation} TypeIdentifier [TypeArguments]
UnannClassOrInterfaceType . {Annotation} TypeIdentifier
[TypeArguments]
UnannInterfaceType:
UnannClassType
UnannTypeVariable:
TypeIdentifier
UnannArrayType:
UnannPrimitiveType Dims
UnannClassOrInterfaceType Dims
UnannTypeVariable Dims
4.3 的产生式仅为方便起见:

Dims:
{Annotation} [ ] {{Annotation} [ ]}

FieldDeclaration 中的每个声明符声明一个字段。如果声明符中没有包含 Identifier,则会发生编译时错误。声明符中的 Identifier 可以用作名称来引用该字段。

FieldDeclaration 中声明多个字段; FieldModifierUnannType 适用于声明中的所有声明符。

FieldModifier 子句在 [8.3.1] 中描述。

8.4.8.3)不同,因为字段隐藏中没有区分静态和非静态字段,而在方法隐藏中区分静态和非静态方法。

一个隐藏字段可以通过使用限定名称(6.5.6.2)来访问,如果它是static,或者通过包含关键字super的字段访问表达式(15.11.2)或者转型为超类类型。

在这方面,字段的隐藏类似于方法的隐藏。

如果字段声明隐藏了另一个字段的声明,则这两个字段不需要具有相同的类型。

一个类从其直接超类和直接超接口继承所有非private字段,这些字段对于类中的代码是可访问的(6.6),并且没有被类中的声明隐藏。

超类的private字段可能对子类可访问 - 例如,如果两个类都是同一个类的成员。然而,private字段永远不会被子类继承。

一个类可能从其超类和超接口或仅从其超接口继承具有相同名称的多个字段。这种情况本身不会导致编译时错误。然而,在类的主体内部尝试通过其简单名称引用任何这样的字段将导致编译时错误,因为引用是模糊的。

可能有几条路径通过接口继承相同的字段声明。在这种情况下,该字段被认为只继承一次,并且可以通过其简单名称引用而不会产生歧义。

示例 8.3-1. 多重继承字段

一个类可以从其超类和超接口或两个超接口继承具有相同名称的两个或更多字段。任何尝试通过其简单名称引用任何模糊继承的字段的编译时错误。可以使用限定名称或包含关键字super的字段访问表达式(15.11.2)来无歧义地访问这些字段。在程序中:

interface Frob  { float v = 2.0f; }
class SuperTest { int   v = 3; }
class Test extends SuperTest implements Frob {
    public static void main(String[] args) {
        new Test().printV();
    }
    void printV() { System.out.println(v); }
}

Test从其超类SuperTest和其超接口Frob继承了两个名为v的字段。这本身是允许的,但由于在方法printV中使用了简单名称v,因此会导致编译时错误:无法确定意图是哪个v

以下变体使用字段访问表达式super.v来引用类SuperTest中声明的名为v的字段,并使用限定名称Frob.v来引用接口Frob中声明的名为v的字段:

interface Frob  { float v = 2.0f; }
class SuperTest { int   v = 3; }
class Test extends SuperTest implements Frob {
    public static void main(String[] args) {
        new Test().printV();
    }
    void printV() {
        System.out.println((super.v + Frob.v)/2);
    }
}

它编译并打印:

2.5

即使两个不同的继承字段具有相同的类型、相同的值并且都是final,任何通过简单名称引用任一字段的尝试都被视为模糊,并导致编译时错误。在程序中:

interface Color        { int RED=0, GREEN=1,  BLUE=2;  }
interface TrafficLight { int RED=0, YELLOW=1, GREEN=2; }
class Test implements Color, TrafficLight {
    public static void main(String[] args) {
        System.out.println(GREEN);  // 编译时错误
        System.out.println(RED);    // 编译时错误
    }
}

引用GREEN被视为模糊并不奇怪,因为类Test继承了具有不同值的两个不同的GREEN声明。这个例子的要点是,对RED的引用也被视为模糊,因为继承了两个不同的声明。两个名为RED的字段恰好具有相同的类型和相同的不变值并不影响这个判断。

示例 8.3-2. 字段的重新继承

如果同一个字段声明通过多条路径从接口继承,该字段被认为只继承一次。在类PaintedPoint中可以在不产生歧义的情况下使用简单名称REDGREENBLUE来引用在接口Colorable中声明的字段。

8.4 方法声明

8.4.1 形式参数

方法或构造函数的形式参数(如果有)由逗号分隔的参数规范器列表指定。每个参数规范器由类型(可选地由final修饰符和/或一个或多个注释前导)和一个标识符(可选地跟随方括号)组成,指定参数的名称。

如果方法或构造函数没有形式参数,也没有接收者参数,则在方法或构造函数的声明中会出现一个空的括号对。

FormalParameterList:
FormalParameter {, FormalParameter}
FormalParameter:
{VariableModifier} UnannType VariableDeclaratorId
VariableArityParameter
VariableArityParameter:
{VariableModifier} UnannType {Annotation} ... Identifier
VariableModifier:
Annotation
final

这里为方便起见显示了来自8.34.3的以下产生:

VariableDeclaratorId:
Identifier [Dims]
VariableDeclaratorId:
Identifier [Dims]
_
Dims:
{Annotation} [ ] {{Annotation} [ ]}

方法或构造函数的形式参数可以是可变数量参数,在类型后面跟着省略号表示。对于一个方法或构造函数,最多只允许一个可变数量参数。如果可变数量参数出现在参数规范器列表的除最后位置之外的任何位置,则会导致编译时错误。

VariableArityParameter的语法中,注意省略号(...)是一个独立的标记(3.11)。可以在它和类型之间放置空格,但这在风格上是不鼓励的。

如果一个方法的最后一个形式参数是可变数量参数,则该方法是一个可变数量方法。否则,它是一个固定数量方法

有关形式参数声明的注释修饰符和接收者参数的规则在9.7.49.7.5中指定。

如果final作为形式参数声明的修饰符出现超过一次,则会导致编译时错误。

形式参数的作用域和遮蔽在6.36.4中指定。

从嵌套类或接口或lambda表达式中引用形式参数受限,如6.5.6.1中指定。

每个方法或构造函数的形式参数声明必须包括一个Identifier,否则会导致编译时错误。

如果一个方法或构造函数声明了两个具有相同名称的形式参数,则会导致编译时错误。(即,它们的声明提到相同的Identifier。)

如果一个声明为final的形式参数在方法或构造函数的主体内部被赋值,则会导致编译时错误。

形式参数的声明类型取决于它是否是可变数量参数:

  • 如果形式参数不是可变数量参数,则如果UnannTypeVariableDeclaratorId中没有方括号对出现,则声明类型由UnannType表示,否则由10.2指定。

  • 如果形式参数是可变数量参数,则声明类型是由10.2指定的数组类型。

如果可变数量参数的声明类型具有不可具体化的元素类型(4.7),则对于可变数量方法的声明会产生编译时未经检查的警告,除非该方法带有@SafeVarargs注解(9.6.4.7)或警告被@SuppressWarnings9.6.4.5)抑制。

当方法或构造函数被调用时(15.12),实际参数表达式的值会在方法或构造函数体执行之前初始化新创建的参数变量,每个都声明了类型。在方法或构造函数体中,FormalParameter中出现的Identifier可以作为简单名称用于引用形式参数。

可变元方法的调用可能包含比形式参数更多的实际参数表达式。所有不对应于可变元参数之前的形式参数的实际参数表达式将被评估,并将结果存储到一个数组中,该数组将传递给方法调用(15.12.4.2)。

以下是实例方法和内部类构造函数中接收器参数的一些示例:

class Test {
    Test(/* ?? ?? */) {}
      // 在顶层类的构造函数中不允许有接收器参数,因为没有可想象的类型或名称。

    void m(Test this) {}
      // OK:实例方法中的接收器参数

    static void n(Test this) {}
      // 非法:静态方法中的接收器参数

    class A {
        A(Test Test.this) {}
          // OK:接收器参数表示立即包围正在构造的A实例的Test实例。

        void m(A this) {}
          // OK:接收器参数表示调用A.m()的A实例。

        class B {
            B(Test.A A.this) {}
              // OK:接收器参数表示立即包围正在构造的B实例的A实例。

            void m(Test.A.B this) {}
              // OK:接收器参数表示调用B.m()的B实例。
        }
    }
}

B的构造函数和实例方法显示了接收器参数的类型可以用限定的TypeName表示,就像其他类型一样;但是内部类构造函数中接收器参数的名称必须使用封闭类的简单名称。

8.10 记录类

8.10.1 记录组件

如果有的话,记录类的记录组件在记录声明的头部中指定。每个记录组件由一个类型(可选地由一个或多个注释前导)和一个标识符组成,该标识符指定记录组件的名称。记录组件对应于记录类的两个成员:隐式声明的private字段和显式或隐式声明的public访问器方法(8.10.3)。

如果记录类没有记录组件,则记录声明的头部中会出现一个空的括号对。

RecordHeader:
( [RecordComponentList] )
RecordComponentList:
RecordComponent {, RecordComponent}
RecordComponent:
{RecordComponentModifier} UnannType Identifier
VariableArityRecordComponent
VariableArityRecordComponent:
{RecordComponentModifier} UnannType {Annotation} ... Identifier
RecordComponentModifier:
Annotation

记录组件可以是可变元记录组件,在类型后面跟着省略号表示。对于记录类,最多只允许一个可变元记录组件。如果可变元记录组件出现在记录组件列表中除了最后位置之外的任何位置,则会导致编译时错误。

有关记录组件声明的注释修饰符的规则在9.7.49.7.5中指定。

如果记录声明中声明了一个记录组件的名称为clonefinalizegetClasshashCodenotifynotifyAlltoStringwait,则会导致编译时错误。

这些是Object中的无参publicprotected方法的名称。禁止将它们作为记录组件的名称有助于避免混淆。首先,每个记录类提供了hashCodetoString的实现,返回记录对象作为整体的表示;它们不能作为记录组件的访问器方法(8.10.3), 因此无法从记录类外部访问这些记录组件。类似地,一些记录类可能提供clone和(遗憾的是)finalize的实现,因此无法通过访问器方法访问名为clonefinalize的记录组件。最后,Object中的getClassnotifynotifyAllwait方法是final的,因此具有相同名称的记录组件无法具有访问器方法(这些访问器方法具有与final方法相同的签名,因此会尝试但无法成功地覆盖它们)。

如果记录声明中声明了两个名称相同的记录组件,则会导致编译时错误。

记录声明的每个记录组件声明必须包含一个Identifier,否则会导致编译时错误。

记录组件的声明类型取决于它是否是可变元记录组件:

  • 如果记录组件不是可变元记录组件,则声明的类型由UnannType表示。

  • 如果记录组件是可变元记录组件,则声明的类型是由10.2指定的数组类型。

如果可变元记录组件的声明类型具有不可具体化的元素类型(4.7),则在可变元记录组件的声明中会发生编译时未经检查的警告,除非规范构造函数(8.10.4)带有@SafeVarargs注解(9.6.4.7)或通过@SuppressWarnings抑制警告(9.6.4.5)。

第9章:接口

9.3 字段(常量)声明

ConstantDeclaration:
{ConstantModifier} UnannType VariableDeclaratorList ;
ConstantModifier:
(其中之一)
Annotation public
static final

有关UnannType,请参阅8.3。以下来自4.38.3的产生规则如下:

VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclaratorId [= VariableInitializer]
VariableDeclaratorId:
Identifier [Dims]
_
Dims:
{Annotation} [ ] {{Annotation} [ ]}
VariableInitializer:
Expression
ArrayInitializer

接口字段声明的注释修饰符规则在9.7.49.7.5中指定。

接口声明体中的每个字段声明都是隐式publicstaticfinal的。对于这些字段,可以冗余地指定这些修饰符中的任何一个或全部。

如果同一个关键字在字段声明的修饰符中出现超过一次,则会导致编译时错误。

如果一个字段声明中出现两个或更多(不同的)字段修饰符,通常情况下,尽管不是必须的,它们应该按照ConstantModifier的产生规则中所示的顺序出现。

如果在UnannTypeVariableDeclaratorId中没有出现括号对,则字段的声明类型由UnannType表示,否则由10.2指定。

ConstantDeclaration中的每个声明符必须包含一个Identifier,否则会导致编译时错误。

接口字段声明的作用域和遮蔽在6.36.4.1中指定。

因为接口字段是static的,其声明引入了一个静态上下文(8.1.3),这限制了引用当前对象的构造的使用。特别地,关键字thissuper在静态上下文中是被禁止的(15.8.315.11.2),未经限定的引用实例变量、实例方法和词法封闭声明的类型参数也是被禁止的(6.5.5.16.5.6.115.12.3)。

如果接口声明了一个带有特定名称的字段,则该字段的声明被认为是隐藏了接口的超级接口中具有相同名称的所有可访问字段的声明。

一个接口可以继承多个具有相同名称的字段。这种情况本身不会导致编译时错误。然而,在接口声明的主体内部尝试通过简单名称引用任何这样的字段将导致编译时错误,因为引用是模糊的。

同一个字段声明从一个接口继承可能有多条路径。在这种情况下,该字段被认为只继承一次,并且可以通过其简单名称引用而不会产生歧义。

示例 9.3-1. 模糊继承字段

如果一个接口继承了两个具有相同名称的字段,例如,因为它的两个直接超级接口声明了具有该名称的字段,则会产生一个模糊的成员。对这个模糊成员的任何使用都将导致编译时错误。在程序中:

interface BaseColors {
    int RED = 1, GREEN = 2, BLUE = 4;
}
interface RainbowColors extends BaseColors {
    int YELLOW = 3, ORANGE = 5, INDIGO = 6, VIOLET = 7;
}
interface PrintColors extends BaseColors {
    int YELLOW = 8, CYAN = 16, MAGENTA = 32;
}
interface LotsOfColors extends RainbowColors, PrintColors {
    int FUCHSIA = 17, VERMILION = 43, CHARTREUSE = RED+90;
}

接口LotsOfColors继承了两个名为YELLOW的字段。只要接口不包含任何通过简单名称引用字段YELLOW的情况,这是可以的。(这样的引用可能出现在字段的变量初始化器中。)

即使接口PrintColors将值3赋给YELLOW而不是值8,在接口LotsOfColors中对字段YELLOW的引用仍将被视为模糊。

示例 9.3-2. 多重继承字段

如果同一个接口从同一个接口多次继承了单个字段,例如,因为这个接口和这个接口的一个直接超级接口都扩展了声明该字段的接口,则只会产生一个成员。这种情况本身不会导致编译时错误。

在前面的示例中,字段REDGREENBLUE通过接口RainbowColors和接口PrintColors多种方式继承到接口LotsOfColors,但在接口LotsOfColors中对字段RED的引用不被视为模糊,因为只涉及一个字段RED的实际声明。

第14章:块、语句和模式

以下更改假定由JEP 440(记录模式)和JEP 441(switch模式匹配)导致的JLS更改已经应用(JLS:JEP440+441)。

14.4 本地变量声明

一个本地变量声明声明并可选择初始化一个或多个本地变量(4.12.3)。

LocalVariableDeclaration:
{VariableModifier} LocalVariableType VariableDeclaratorList
LocalVariableType:
UnannType
var

参见UnannType8.3。这里为方便起见展示了来自4.38.38.4.1的以下产生:

VariableModifier:
Annotation
final
VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclaratorId [= VariableInitializer]
VariableDeclaratorId:
Identifier [Dims]
VariableDeclaratorId:
Identifier [Dims]
_
Dims:
{Annotation} [ ] {{Annotation} [ ]}
VariableInitializer:
Expression
ArrayInitializer

本地变量声明可以出现在以下位置:

  • 块中的本地变量声明语句(14.4.2

  • 基本for语句的头部(14.14.1

  • 增强for语句的头部(14.14.2

  • try-with-resources语句的资源规范(14.20.3

  • 模式(14.30.1

有关本地变量声明的注解修饰符的规则在9.7.49.7.5中指定。

如果关键字final作为本地变量声明的修饰符出现,则本地变量是一个final变量(4.12.4)。

如果final作为本地变量声明的修饰符出现超过一次,则会导致编译时错误。

如果一个本地变量声明不包括Identifier并且没有初始化器,则在以下位置使用它会导致编译时错误:

  • 块中的本地变量声明语句(14.4.2

  • 基本for语句的头部(14.14.1

如果LocalVariableTypevar,并且以下任一情况为真,则会导致编译时错误:

  • 列出了多个VariableDeclarator

  • VariableDeclaratorId有一个或多个方括号对。

  • VariableDeclarator缺少初始化器。

  • VariableDeclarator的初始化器是一个ArrayInitializer

  • VariableDeclarator的初始化器包含对该变量的引用。

示例 14.4-1. 使用var声明的本地变量

以下代码说明了限制使用var的规则:

var a = 1;            // 合法
var b = 2, c = 3.0;   // 非法:多个声明符
var d[] = new int[4]; // 非法:额外的方括号对
var e;                // 非法:没有初始化器
var f = { 6 };        // 非法:数组初始化器
var g = (g = 7);      // 非法:初始化器中的自引用

这些限制有助于避免关于var表示的类型的混淆。

14.11 switch语句

switch语句根据表达式的值将控制转移到多个语句或表达式中的一个。

SwitchStatement:
switch ( Expression ) SwitchBlock

Expression称为选择器表达式。选择器表达式的类型必须是charbyteshortint或引用类型,否则会导致编译时错误。

14.11.1 switch

switch语句和switch表达式的主体都称为switch块。本小节介绍适用于所有switch块的一般规则,无论它们出现在switch语句中还是switch表达式中。其他小节介绍适用于switch语句中的switch块(14.11.2)或switch表达式中的switch块(15.28.1)的其他规则。

SwitchBlock:
{ SwitchRule {SwitchRule} }
{ {SwitchBlockStatementGroup} {SwitchLabel :} }
SwitchRule:
SwitchLabel -> Expression ;
SwitchLabel -> Block
SwitchLabel -> ThrowStatement
SwitchBlockStatementGroup:
SwitchLabel : { SwitchLabel :} BlockStatements
SwitchLabel:
case CaseConstant {, CaseConstant}
case null [, default]
case CasePattern [ Guard ]
default
SwitchLabel:
case CaseConstant {, CaseConstant}
case null [, default]
case CasePattern{, CasePattern } [ Guard ]
default
CaseConstant:
条件表达式
CasePattern:
模式
Guard:
when 表达式

一个switch块可以包含以下内容之一:

  • Switch规则,使用->引入switch规则表达式switch规则块switch规则throw语句;或

  • Switch标记语句组,使用:引入switch标记块语句

每个switch规则和switch标记语句组都以switch标签开头,可以是case标签或default标签。对于switch标记语句组,允许有多个switch标签。

case标签由case常量列表或case模式一个或多个组成。

  • 对于具有case常量的case标签,每个case常量必须是(1)null文字、(2)常量表达式(15.29)或(3)枚举常量(简单或限定)的名称(8.9.1);否则会发生编译时错误。单个null case常量也可以与default关键字配对。

  • 对于具有case模式的case标签,如果任何case模式声明一个或多个模式变量,则会发生编译时错误。

    如果一个case标签有多个声明模式变量的case模式,那么如果应用case标签,则不清楚哪个变量将被初始化。例如,在代码片段switch (obj) { case Integer i, Boolean b -> { ... } ... }中,在->右侧的块中不清楚哪个模式变量ib将被初始化,因此这是一个编译时错误。代码片段switch(obj) { case Integer i, Boolean _ -> { ... } ... }仍然导致编译时错误,因为仍然不清楚模式变量i是否将被初始化。代码片段switch (obj) { case Integer _, Boolean _ -> { ... } ... }不会导致编译时错误。

具有case模式的case标签可以具有可选的when表达式,称为守卫,它表示与模式匹配的值的进一步测试。如果(i)它没有守卫,或者(ii)它具有值为true的常量表达式(15.29)的守卫,则称case标签为未守卫;否则为守卫

switch标签及其case常量和case模式称为与switch块关联

对于给定的switch块,以下两个条件都必须为真,否则会发生编译时错误:

  • 与switch块关联的case常量中的任何两个值不能相同。
  • 与switch块关联的default标签不能超过一个。

case标签关联的任何守卫必须具有类型booleanBoolean。任何守卫中使用但未声明的变量必须是final或有效最终的(4.12.4),不能被赋值(15.26)、递增([15.14.2])或递减([15.14.3]),否则会发生编译时错误。如果守卫是值为false的常量表达式(15.29),则会发生编译时错误。

switch语句或switch表达式的switch块与选择器表达式的类型Tswitch兼容的,如果以下所有条件都为真:

  • 如果与switch块关联任何null常量,则T是引用类型。

  • 如果T是枚举类型,则与switch块关联的每个case常量,如果是枚举常量的名称,则是类型T的枚举常量的名称。

  • 如果T不是枚举类型,则与switch块关联的每个case常量,如果是枚举常量的限定名称,则是与T兼容的枚举常量的限定名称(5.2)。

  • 与switch块关联的每个case常量,如果是常量表达式,则与T兼容,并且TcharbyteshortintCharacterByteShortIntegerString之一。

  • 与switch块关联的每个模式pp适用于类型T14.30.3)。

switch块不适用于booleanlongfloatdouble类型。switch语句或switch表达式的选择器表达式不能具有这些类型之一。

switch语句或switch表达式的switch块必须与选择器表达式的类型兼容,否则会发生编译时错误。

在switch块中的switch标签被称为支配的,如果对于它适用的每个值,前面的switch标签也将适用。如果switch块中的任何switch标签被支配,则会发生编译时错误。确定switch标签是否被支配的规则如下:

  • 如果在switch块中有一个带有case模式qcase标签被支配,那么如果在switch块中有一个前置的无保护的带有case模式pcase标签,并且p支配q,则q被支配(14.30.3)。

    一个模式支配另一个模式的定义基于类型。例如,类型模式Object o支配类型模式String s,因此以下代码会导致编译时错误:

    Object obj = ...
    switch (obj) {
        case Object o ->
            System.out.println("一个对象");
        case String s   ->                 // 错误 - 被支配的case标签
            System.out.println("一个字符串");
    }

    带有case模式的有保护case标签被不带有保护的相同模式的case标签支配。例如,以下代码会导致编译时错误:

    String str = ...;
    switch (str) {
        case String s ->
            System.out.println("一个字符串");
        case String s when s.length() == 2 ->  // 错误 - 被支配的case标签
            System.out.println("长度为两个字符的字符串");
        ...
    }

    另一方面,带有case模式的有保护case标签不被认为支配具有相同case模式的无保护case标签。这允许以下常见的模式编程风格:

    Integer j = ...;
    switch (j) {
        case Integer i when i <= 0 ->
            System.out.println("小于或等于零");
        case Integer i ->
            System.out.println("一个整数");
    }

    唯一的例外是当保护是一个具有值为true的常量表达式时,例如:

    Integer j = ...;
    switch (j) {
        case Integer i when true ->            // 允许但为什么写这个?
            System.out.println("一个整数");
        case Integer i ->                     // 错误 - 被支配的case标签
            System.out.println("一个整数");
    }

    如果一个case标签具有多个模式,那么如果任何一个模式被前置的无保护case标签中出现的模式支配,则该case标签被支配;例如Integer _被前置的无保护case中的Number _支配:

    Object o = ...
    switch (o) {
        case Number _ ->
            System.out.println("一个数字");
        case Integer _, String _ ->             // 错误 - 被支配的case模式:`Integer _`
            System.out.println("一个整数或一个字符串");
    }
  • 如果一个带有case常量ccase标签被支配,那么以下情况之一成立:

    • c是原始类型S的常量表达式,并且在switch块中有一个前置的带有S的包装类为无条件模式的case标签。

    • c是引用类型T的常量表达式,并且在switch块中有一个前置的带有T类型为无条件模式的case标签。

    • c是类型T的枚举常量,并且在switch块中有一个前置的带有T类型为无条件模式的case标签。

    例如,带有Integer类型模式的case标签支配整数字面值的case标签:

    Integer j = ...;
    switch (j) {
        case Integer i ->
            System.out.println("一个整数");
        case 42 ->                              // 错误 - 被支配!
            System.out.println("42!");
    }

    对保护的分析——一般情况下是不可判定的。例如,即使如果选择器表达式的值为42,以下代码也会导致编译时错误:

    Integer j = ...;
    switch (j) {
        case Integer i when i != 42 ->
            System.out.println("一个不是42的整数");
        case 42 ->                                  // 错误 - 被支配!
            System.out.println("42!");
    }

    任何带有case常量的case标签应该出现在带有case模式的标签之前;例如:

    Integer j = ...;
    switch (j) {
        case 42 ->
            System.out.println("42");
        case Integer i when i < 50 ->
            System.out.println("小于50的整数");
        case Integer i  ->
            System.out.println("一个整数");
    }
  • 如果在switch块中有一个带有case模式的case标签被支配,那么如果在switch块中有一个前置的default标签。

  • 如果在switch块中有一个带有null case常量的case标签被支配,那么如果在switch块中有一个前置的default标签。

    如果使用,default标签应该出现在switch块中的最后。

    出于历史原因,default标签可以出现在没有null case常量或case模式的case标签之前。

    int i = ...;
    switch(i) {
        default ->
            System.out.println("其他整数");
        case 42 -> // 允许
            System.out.println("42");
    }

    这种风格在新代码中不推荐使用。

  • 如果在switch块中有一个带有nulldefaultcase标签被支配,那么如果在switch块中有一个前置的带有case模式p的无保护的case标签,其中p对于选择器表达式的类型是无条件的(14.30.3)。

  • 如果在switch块中有一个带有nulldefaultcase标签被支配,那么如果在switch块中有一个前置的带有case模式p的无保护的case标签,其中p对于选择器表达式的类型是无条件的(14.30.3)。

    带有对于选择器表达式类型是无条件的case模式的case标签,如其名称所示,将匹配每个值,因此表现类似于default标签。一个switch块不能有多个像default一样行为的switch标签。

  • 如果在switch块中存在一个带有case模式p1,...,pnn > 1)的case标签,其中其中一个模式pi1 ≤ i < n)支配另一个模式pji < j ≤ n),则会导致编译时错误。

    如果在由switch标记的语句组成的switch块中,一个语句被标记为带有声明一个或多个模式变量的case模式,并且:

    • 在switch块中的前一个语句可以正常完成(14.22),或

    • 该语句被标记为多个switch标签。

    第一个条件防止一个语句组从一个语句组“穿透”到另一个语句组而不初始化模式变量。例如,如果一个由case Integer i标记的语句可以从前一个语句组到达,那么模式变量i将不会被初始化:

    Object o = "Hello";
    switch (o) {
        case String s:
            System.out.println("字符串:" + s );  // 没有break!
        case Integer i:
            System.out.println(i + 1);            // 错误!可以到达
                                                  // 而不匹配模式`Integer i`
        default:
    }

    由switch标记的switch块允许多个标签应用于一个语句组。第二个条件防止一个语句组根据一个标签执行而不初始化另一个标签的模式变量。例如:

    Object o = "Hello World";
    switch (o) {
        case String s:
        case Integer i:
            System.out.println(i + 1);  // 错误!可以到达
                                        // 而不匹配模式`Integer i`
        default:
    }
    
    Object obj = null;
    switch (obj) {
        case null:
        case String s:
            System.out.println(s);      // 错误!可以到达
                                        // 而不匹配模式`String s`
        default:
    }

    这两个条件仅在case模式声明模式变量时适用。相比之下,以下示例没有问题:

    record R() {}
    record S() {}
    
    Object o = "Hello World";
    switch (o) {
        case String s:
            System.out.println(s);        // 没有break!
        case R():
            System.out.println("它是R或字符串");
            break;
        default:
    }
    
    Object ob = new R();
    switch (ob) {
        case R():
        case S():                         // 多个case标签!
            System.out.println("要么是R要么是S");
            break;
        default:
    }
    
    Object obj = null;
    switch (obj) {
        case null:
        case R():                         // 多个case标签!
            System.out.println("要么是null要么是R");
            break;
        default:
    }
    14.11.1.2 在运行时确定哪个Switch标签适用

    执行switch语句(14.11.3)和评估switch表达式(15.28.2)都需要确定与switch块相关联的switch标签是否适用于选择器表达式的值。这样进行:

    1. 如果值是空引用,则适用具有空case常量的case标签。

    2. 如果值不是空引用,则我们按照以下方式确定适用于该值的开关块中的第一个(如果有)case标签:

      • 具有非空case常量ccase标签适用于CharacterByteShortInteger类型的值,如果该值首先经过解箱转换(5.1.8),并且常量c等于解箱后的值。

        任何解箱转换必须正常完成,因为被解箱的值保证不是空引用。

        相等性是根据==运算符定义的(15.21)。

      • 具有非空case常量ccase标签适用于不是CharacterByteShortInteger类型的值,如果常量c等于该值。

        相等性是根据==运算符定义的,除非该值是String,在这种情况下,相等性是根据String类的equals方法定义的。

      • 确定case模式p适用于值的过程首先通过检查值是否与模式p匹配进行(14.30.2)。

        确定具有case模式p1,...,pnn ≥ 1)的case标签适用于值的过程是通过确定适用的第一个(如果有)case模式pi1 ≤ i ≤ n)进行的。

        如果确定哪个case模式适用的过程突然完成,则确定哪个开关标签适用的过程因同样的原因突然完成。

        确定case模式p适用于值的过程首先通过检查值是否与模式p匹配进行(14.30.2)。

        如果模式匹配突然完成,则确定哪个 开关标签case模式适用的过程因同样的原因突然完成。

        如果模式匹配成功且case 标签模式是无保护的,则此case 标签模式适用。

        如果模式匹配成功且case 标签模式是有保护的,则评估该保护条件。如果结果是Boolean类型,则会进行解箱转换(5.1.8)。

        如果保护条件的评估或随后的解箱转换(如果有)因某种原因突然完成,则确定哪个 开关标签模式适用的过程因同样的原因突然完成。

        否则,如果结果值为true,则case 标签模式适用。 :::

      • case null, default标签适用于每个值

    3. 如果值不是空引用,并且根据第2步的规则没有适用的case标签,则如果开关块关联有default标签,则适用该标签。

    单个case标签可以包含多个case常量。如果其常量中的任何一个等于选择器表达式的值,则该标签适用于选择器表达式的值。例如,在以下代码中,如果枚举变量day是所示枚举常量之一,则case标签匹配:

    switch (day) {
        ...
        case SATURDAY, SUNDAY :
            System.out.println("It's the weekend!");
            break;
        ...
    }

    如果适用具有case模式的case标签,则这是因为成功地将值与模式进行了模式匹配(14.30.2)。如果值成功匹配模式,则模式匹配的过程将初始化模式声明的任何模式变量。

    case标签未能匹配的情况下才考虑 default标签,即使其中一些标签出现在 default标签之后。但是,后续标签只能使用非 null的case常量( 14.11.1),并且作为一种风格,鼓励程序员将 default标签放在最后。

    switch语句的主体可以是一个语句,并且具有 case标签的语句不必立即包含在该语句中。考虑简单的循环:

    for (i = 0; i < n; ++i) foo();
    n已知为正数。在C或C++中可以使用称为 Duff's device的技巧来展开循环,但这不是Java编程语言中的有效代码:

    int q = (n+7)/8;
    switch (n%8) {
        case 0: do { foo();    // Great C hack, Tom,
        case 7:      foo();    // but it's not valid here.
        case 6:      foo();
        case 5:      foo();
        case 4:      foo();
        case 3:      foo();
        case 2:      foo();
        case 1:      foo();
                } while (--q > 0);
    }

    14.14 for语句

    14.14.2 增强的for语句

    for语句的形式如下:

    EnhancedForStatement:
    for ( LocalVariableDeclaration : Expression )
    Statement
    EnhancedForStatementNoShortIf:
    for ( LocalVariableDeclaration : Expression )
    StatementNoShortIf
    4.38.38.4.114.4的产生式仅供参考:

    LocalVariableDeclaration:
    {VariableModifier} LocalVariableType VariableDeclaratorList
    VariableModifier:
    Annotation
    final
    LocalVariableType:
    UnannType
    var
    VariableDeclaratorList:
    VariableDeclarator {, VariableDeclarator}
    VariableDeclarator:
    VariableDeclaratorId [= VariableInitializer]
    VariableDeclaratorId:
    Identifier [Dims]
    VariableDeclaratorId:
    Identifier [Dims]
    _
    Dims:
    {Annotation} [ ] {{Annotation} [ ]}

    Expression的类型必须是数组类型(10.1)或原始类型Iterable的子类型,否则将出现编译时错误。

    for语句的头部 要么声明一个局部变量,其名称是 VariableDeclaratorId给定的标识符 ,要么声明一个未命名的局部变量(6.1)。执行增强 for语句时,在循环的每次迭代中,该局部变量被初始化为表达式生成的 Iterable或数组的连续元素。

    for语句的头部声明的局部变量的作用域和遮蔽在 6.36.4中指定。

    6.5.6.1中指定。

    for语句的头部声明的局部变量的类型 T确定如下:

    • 如果LocalVariableTypeUnannType,并且UnannTypeVariableDeclaratorId中没有出现括号对,则T是由UnannType表示的类型。

    • 如果LocalVariableTypeUnannType,并且UnannTypeVariableDeclaratorId中出现了括号对,则T10.2指定。

    • 如果LocalVariableTypevar,则让RExpression的类型派生,如下:

      • 如果Expression具有数组类型,则R是数组类型的组件类型。

      • 否则,如果Expression具有Iterable<X>的子类型,其中X是某种类型,则RX

      • 否则,Expression具有原始类型Iterable的子类型,RObject

      T是关于R提及的所有合成类型变量的上投影(4.10.5)。

    增强的for语句的确切含义,其标题声明一个局部变量Identifier,通过以下方式转换为基本for语句:

    • 如果Expression的类型是Iterable的子类型,则基本for语句具有以下形式:

      for (I #i = Expression.iterator(); #i.hasNext(); ) {
          {VariableModifier} T Identifier = (TargetType) #i.next();
          Statement
      }
      

      其中:

      • 如果Expression的类型是Iterable<X>的子类型,其中X是某种类型参数,则I是类型java.util.Iterator<X>。否则,I是原始类型java.util.Iterator

      • #i是一个自动生成的标识符,与增强的for语句发生的位置的任何其他标识符(自动生成的或其他)不同。

      • {VariableModifier}如增强的for语句的标题中所示。

      • T是如上确定的局部变量的类型。

      • 如果T是引用类型,则TargetTypeT。否则,TargetTypeI的类型参数的捕获转换的上界(5.1.10),如果I是原始的,则为Object

    • 否则,Expression必然具有数组类型S[],基本for语句具有以下形式:

      S[] #a = Expression;
      L1: L2: ... Lm:
      for (int #i = 0; #i < #a.length; #i++) {
          {VariableModifier} T Identifier = #a[#i];
          Statement
      }
      

      其中:

      • L1 ... Lm是增强的for语句之前的(可能为空的)标签序列。

      • #a#i是自动生成的标识符,与增强的for语句发生的位置的任何其他标识符(自动生成的或其他)不同。

      • {VariableModifier}如增强的for语句的标题中所示。

      • T是如上确定的局部变量的类型。

    例如,以下代码:

    List<? extends Integer> l = ...
    for (float i : l) ...

    将被转换为:

    for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) {
        float #i0 = (Integer)#i.next();
        ...

    示例 14.14-1. 增强的for和数组

    以下程序计算整数数组的总和,展示了如何对数组使用增强的for

    int sum(int[] a) {
        int sum = 0;
        for (int i : a) sum += i;
        return sum;
    }

    示例 14.14-2. 增强的for和拆箱转换

    以下程序将增强的for语句与自动拆箱结合起来,将直方图转换为频率表:

    Map<String, Integer> histogram = ...;
    double total = 0;
    for (int i : histogram.values())
        total += i;
    for (Map.Entry<String, Integer> e : histogram.entrySet())
        System.out.println(e.getKey() + " " + e.getValue() / total);
    }

    如果增强的for语句的标题声明了一个未命名的局部变量,则其确切含义如上转换为基本for语句,但使用未命名的局部变量代替Identifier

    14.20 try语句

    14.20.3 try资源

    try资源语句使用在执行try块之前初始化并在执行try块后以与初始化相反的顺序自动关闭的变量(称为资源)。当资源被自动关闭时,catch子句和finally子句通常是不必要的。

    TryWithResourcesStatement:
    try ResourceSpecification Block [Catches] [Finally]
    ResourceSpecification:
    ( ResourceList [;] )
    ResourceList:
    Resource {; Resource}
    Resource:
    LocalVariableDeclaration
    VariableAccess
    VariableAccess:
    ExpressionName
    FieldAccess

    这里为方便起见显示了来自4.38.38.4.114.4的以下产生式:

    LocalVariableDeclaration:
    {VariableModifier} LocalVariableType VariableDeclaratorList
    VariableModifier:
    Annotation
    final
    LocalVariableType:
    UnannType
    var
    VariableDeclaratorList:
    VariableDeclarator {, VariableDeclarator}
    VariableDeclarator:
    VariableDeclaratorId [= VariableInitializer]
    VariableDeclaratorId:
    Identifier [Dims]
    VariableDeclaratorId:
    Identifier [Dims]
    _
    Dims:
    {Annotation} [ ] {{Annotation} [ ]}
    VariableInitializer:
    Expression
    ArrayInitializer

    请参阅8.3中的UnannType

    资源规范表示try资源语句的资源,可以通过声明带有初始化表达式的局部变量或引用现有变量来实现。现有变量通过表达式名称(6.5.6)或字段访问表达式(15.11)引用。

    在资源规范中声明的局部变量的规则在14.4中指定。此外,以下所有条件必须为真,否则将出现编译时错误:

    • VariableDeclaratorList由单个VariableDeclarator组成。

    • VariableDeclarator具有初始化程序。

    • VariableDeclaratorId没有括号对。

    在资源规范中声明的局部变量的作用域和遮蔽在6.36.4中指定。

    从嵌套类或接口,或lambda表达式中引用局部变量受限,如6.5.6.1中指定。

    在资源规范中声明的局部变量的类型在14.4.1中指定。

    在资源规范中声明的局部变量的类型,或在资源规范中引用的现有变量的类型,必须是AutoCloseable的子类型,否则将出现编译时错误。

    资源规范声明两个同名局部变量是编译时错误。

    6.1)。

    资源是final的,即:

    • 在资源规范中声明的局部变量如果没有显式声明为final,则会隐式声明为final4.12.4)。

    • 在资源规范中引用的现有变量必须是在try资源语句之前明确定义的final或有效final变量(16),否则将出现编译时错误。

    资源按从左到右的顺序初始化。如果一个资源初始化失败(即,其初始化表达式抛出异常),那么try-with-resources语句到目前为止已经初始化的所有资源都将被关闭。如果所有资源成功初始化,try块将正常执行,然后try-with-resources语句中所有非空资源都将被关闭。

    资源按照初始化顺序的相反顺序关闭。只有初始化为非空值的资源才会被关闭。一个资源的关闭异常不会阻止其他资源的关闭。如果之前由初始化程序、try块或资源关闭引发了异常,则此类异常将被抑制

    一个资源规范指示多个资源的try-with-resources语句被视为多个try-with-resources语句,每个语句都有指示单个资源的资源规范。当一个指示有n个资源(n > 1)的try-with-resources语句被转换时,结果是一个有n-1个资源的try-with-resources语句。经过n次这样的转换后,会有n个嵌套的try-catch-finally语句,整体转换完成。

    14.20.3.1 基本try-with-resources

    一个没有catch子句或finally子句的try-with-resources语句称为基本try-with-resources语句。

    如果一个基本try-with-resources语句的形式为:

    try (VariableAccess ...)
        Block
    

    那么资源首先通过以下转换转换为本地变量声明:

    try (T #r = VariableAccess ...) {
        Block
    }
    

    T是由VariableAccess表示的变量的类型,#r是一个自动生成的标识符,与try-with-resources语句发生的地方的任何其他标识符(自动生成的或其他)都不同。然后根据本节的其余部分转换try-with-resources语句。

    一个形式为:

    try ({VariableModifier} R Identifier = Expression ...)
        Block
    

    的基本try-with-resources语句的含义由以下转换为本地变量声明和try-catch-finally语句给出:

    {
        final {VariableModifierNoFinal} R Identifier = Expression;
        Throwable #primaryExc = null;
    
        try ResourceSpecification_tail
            Block
        catch (Throwable #t) {
            #primaryExc = #t;
            throw #t;
        } finally {
            if (Identifier != null) {
                if (#primaryExc != null) {
                    try {
                        Identifier.close();
                    } catch (Throwable #suppressedExc) {
                        #primaryExc.addSuppressed(#suppressedExc);
                    }
                } else {
                    Identifier.close();
                }
            }
        }
    }
    

    {VariableModifierNoFinal}被定义为没有final{VariableModifier},如果存在的话。

    #t#primaryExc#suppressedExc是自动生成的标识符,与try-with-resources语句发生的地方的任何其他标识符(自动生成的或其他)都不同。

    另外,一个形式为:

    try ({VariableModifier} R _ = Expression ...)
        Block
    

    的基本try-with-resources语句的含义由以下转换为本地变量声明和try-catch-finally语句给出:

    {
        final {VariableModifierNoFinal} R #i = Expression;
        Throwable #primaryExc = null;
    
        try ResourceSpecification_tail
            Block
        catch (Throwable #t) {
            #primaryExc = #t;
            throw #t;
        } finally {
            if (#i != null) {
                if (#primaryExc != null) {
                    try {
                        #i.close();
                    } catch (Throwable #suppressedExc) {
                        #primaryExc.addSuppressed(#suppressedExc);
                    }
                } else {
                    #i.close();
                }
            }
        }
    }
    

    {VariableModifierNoFinal}被定义为没有final{VariableModifier},如果存在的话。

    #t#primaryExc#suppressedExc#i是自动生成的标识符,与try-with-resources语句发生的地方的任何其他标识符(自动生成的或其他)都不同。

    如果资源规范指示一个资源,则ResourceSpecification_tail为空(并且try-catch-finally语句本身不是try-with-resources语句)。

    如果资源规范指示n > 1个资源,则ResourceSpecification_tail由资源规范中指示的第2个、第3个、...、第n个资源组成,顺序相同(并且try-catch-finally语句本身是try-with-resources语句)。

    基本try-with-resources语句的可达性和明确赋值规则由上述转换隐含指定。

    在管理单个资源的基本try-with-resources语句中:

    • 如果资源的初始化因值Vthrow而突然完成,则try-with-resources语句因值Vthrow而突然完成。

    • 如果资源的初始化正常完成,并且try块因值Vthrow而突然完成,则:

      • 如果资源的自动关闭正常完成,则try-with-resources语句因值Vthrow而突然完成。

      • 如果资源的自动关闭因值V2throw而突然完成,则try-with-resources语句因值Vthrow而突然完成,V2被添加到V的抑制异常列表中。

    • 如果资源的初始化正常完成,并且try块正常完成,并且资源的自动关闭因值Vthrow而突然完成,则try-with-resources语句因值Vthrow而突然完成。

    在管理多个资源的基本try-with-resources语句中:

    • 如果一个资源的初始化因值Vthrow而突然完成,则:

      • 如果所有成功初始化的资源(可能为零)的自动关闭正常完成,则try-with-resources语句因值Vthrow而突然完成。

      • 如果所有成功初始化的资源(可能为零)的自动关闭因值V1...Vnthrow而突然完成,则try-with-resources语句因值Vthrow而突然完成,剩余的值V1...Vn被添加到V的抑制异常列表中。

    • 如果所有资源的初始化正常完成,并且try块因值Vthrow而突然完成,则:

      • 如果所有初始化的资源的自动关闭正常完成,则try-with-resources语句因值Vthrow而突然完成。

      • 如果一个或多个初始化的资源的自动关闭因值V1...Vnthrow而突然完成,则try-with-resources语句因值Vthrow而突然完成,剩余的值V1...Vn被添加到V的抑制异常列表中。

    • 如果每个资源的初始化都正常完成,并且try块正常完成,则:

      • 如果一个初始化资源的自动关闭因值Vthrow而突然完成,并且所有其他初始化资源的自动关闭正常完成,则try-with-resources语句因值Vthrow而突然完成。

      • 如果多个初始化资源的自动关闭因值V1...Vnthrow而突然完成(其中V1是最右侧资源未能关闭的异常,Vn是最左侧资源未能关闭的异常),则try-with-resources语句因值V1throw而突然完成,剩余的值V2...Vn被添加到V1的抑制异常列表中。

    14.20.3.2 扩展try-with-resources

    至少有一个catch子句和/或finally子句的try-with-resources语句称为扩展try-with-resources语句。

    扩展try-with-resources语句的含义:

    
    try ResourceSpecification
        Block
    [Catches]
    [Finally]
    

    由以下转换为嵌套在try-catchtry-finallytry-catch-finally语句内的基本try-with-resources语句给出:

    
    try {
        try ResourceSpecification
            Block
    }
    [Catches]
    [Finally]
    

    转换的效果是将资源规范“放入”try语句中。这允许扩展try-with-resources语句的catch子句捕获由任何资源的自动初始化或关闭引起的异常。

    此外,所有资源将在finally块执行时已关闭(或尝试关闭),符合finally关键字的意图。

    14.30 模式

    14.30.1 模式的种类

    假设已应用JEP 440(记录模式)和JEP 441(switch模式匹配)导致的JLS更改(JLS:JEP440+441)。

    类型模式用于测试一个值是否是模式中出现的类型的实例。 记录模式用于测试一个值是否是记录类类型的实例,并且如果是,则对记录组件值执行递归模式匹配。

    模式
    类型模式
    记录模式
    类型模式
    局部变量声明
    记录模式
    引用类型 ( [ 模式列表组件模式列表 ] )
    模式列表
    模式 { , 模式 }
    组件模式列表
    组件模式 { , 组件模式 }
    组件模式
    模式
    未命名模式
    未命名模式
    _

    以下来自4.38.38.4.114.4的产生式仅供参考:

    局部变量声明:
    {变量修饰符} 局部变量类型 变量声明符列表
    变量修饰符:
    注解
    final
    局部变量类型:
    非注释类型
    var
    变量声明符列表:
    变量声明符 {, 变量声明符}
    变量声明符:
    变量声明符标识符 [= 变量初始化器]
    变量声明符标识符:
    标识符 [维度]
    变量声明符标识符:
    标识符 [维度]
    _
    维度:
    {注解} [ ] {{注解} [ ]}

    查看非注释类型8.3

    在记录模式的嵌套模式列表中不出现的模式称为顶层模式;否则称为嵌套模式。

    类型模式声明正好一个模式变量。 此模式变量可以是一个未命名模式变量(用_表示),否则 在局部变量声明中的标识符指定模式变量的名称。在局部变量声明中的标识符指定模式变量的名称。

    在类型模式中声明的模式变量的规则在14.4中指定。此外,以下所有条件必须为真,否则将出现编译时错误:

    • 顶层类型模式中的局部变量类型表示一个引用类型(而且不是var)。

    • 变量声明符列表由单个变量声明符组成。

    • 变量声明符没有初始化程序。

    • 变量声明符标识符没有括号对。

    在顶层类型模式中声明的模式变量的类型是由局部变量类型表示的引用类型。

    在嵌套类型模式中声明的模式变量的类型如下确定:

    • 如果局部变量类型非注释类型,则模式变量的类型由非注释类型表示

    • 如果局部变量类型var,则类型模式必须嵌套在具有类型R的记录模式的组件模式列表中。让TR中相应组件字段的类型。模式变量的类型是T相对于T提到的所有合成类型变量的上投影。

      考虑以下记录声明:

      record R<T>(ArrayList<T> r){}

      给定模式R<String>(var r),模式变量r的类型因此为ArrayList<String>

    如果类型模式作为记录模式的模式列表中的元素出现,并且记录模式的类型为R,则类型模式被称为空匹配,并且对于类型U,类型模式对于类型U是无条件的(14.30.3)。

    请注意,类型模式的这种编译时属性用于模式匹配过程(14.30.2),因此与类型模式关联以在运行时使用。

    记录模式由一个引用类型和一个嵌套的组件模式列表组成。如果引用类型不是记录类类型(8.10),则会出现编译时错误。

    如果引用类型是原始类型,则记录模式的类型将被推断,如[18.5.5]中所述。如果无法为记录模式推断类型,则会出现编译时错误。

    否则,记录模式的类型为引用类型

    记录模式的嵌套组件模式列表的长度必须与由引用类型命名的记录类声明中的记录组件列表的长度相同;否则会出现编译时错误。

    目前,不支持可变数量的记录模式。这可能在未来版本的Java编程语言中得到支持。

    记录模式声明由在嵌套的组件模式列表中声明的模式中声明的模式变量(如果有)组成。

    未命名模式是一种特殊模式,只能作为具有类型R的记录模式的组件模式列表中的元素出现。让TR中相应组件字段的类型。未命名模式不声明任何模式变量,其类型被定义为T相对于T提到的所有合成类型变量的上投影。未命名模式始终是空匹配的。

    14.30.2 模式匹配

    模式匹配是在运行时针对模式测试值的过程。模式匹配与语句执行(14.1)和表达式评估(15.1)不同。 如果一个值成功匹配一个模式,则模式匹配过程将初始化模式声明的所有模式变量(如果有)。

    模式匹配过程可能涉及表达式评估或语句执行。因此,如果表达式的评估或语句的执行完成异常,则说模式匹配突然完成。突然完成始终有一个关联的原因,这个原因总是一个具有给定值的throw。如果没有突然完成,则说模式匹配正常完成

    确定值是否匹配模式以及初始化模式变量的规则如下:

    • 空引用匹配一个 类型模式空匹配模式 如果类型模式是空匹配14.30.1);否则不匹配

      如果空引用匹配,则如果有的话类型空匹配模式声明的模式变量将被初始化为空引用。

      如果空引用不匹配,则如果有的话类型空匹配模式声明的模式变量将不被初始化。

    • 不是空引用的值匹配类型为T的类型模式,如果v可以被强制转换为T而不引发ClassCastException;否则不匹配

      如果v匹配,则由类型模式声明的模式变量将被初始化为v

      如果v不匹配,则由类型模式声明的模式变量将不被初始化。

    • 空引用不匹配记录模式。

      在这种情况下,由记录模式声明的任何模式变量都不会被初始化。

    • 不是空引用的值匹配类型为R且具有嵌套组件模式列表L的记录模式,如果(i)v可以被强制转换为R而不引发ClassCastException;并且(ii)v的每个记录组件与L中的相应模式匹配;否则不匹配

      v的每个记录组件是通过调用与该组件对应的v的访问器方法来确定的。如果调用访问器方法的执行因原因S而突然完成,则通过抛出具有原因SMatchException来突然完成模式匹配。

      出现在嵌套组件模式列表中的模式中声明的任何模式变量仅在所有列表中的模式都匹配时才被初始化。