Skip to content

Latest commit

 

History

History
1420 lines (785 loc) · 482 KB

14-块和语句.md

File metadata and controls

1420 lines (785 loc) · 482 KB

第十四章 块和语句

程序的执行序列由语句控制,其为了效果而被执行,并且没有值。

某些语句将其他语句作为他们结构的一部分包含;这种其他语句是该语句的子语句。我们说,语句 S 直接包含语句 U,如果没有不同于 S 和 U 的语句 T,S 包含 T,T 包含 U。同样的方式,某些语句将表达式(15(Expressions))作为他们结构的一部分包含。

本章的第一节讨论了语句(14.1)正常完成和突然完成之间的区别。剩余章节的大多数解释了各种种类的语句,描述了他们的正常行为的细节和突然完成时的任何特殊处理的细节。

首先解释块(14.2),然后是局部类声明(14.3)和局部变量声明语句(14.4).

接下来,解释一个避开熟悉的悬挂 else 问题(14.5)的语法动作。

本章的最后一节(14.21)论述了在某种技术意义上每个语句可达的要求。

14.1. 语句的正常和突然完成

每个语句都有一个正常的执行模式,在其中进行某些计算步骤。以下各节描述了每种语句的正常执行模式。

如果所有步骤都按描述进行,并且没有任何突然完成迹象,则称该语句是正常完成的。但是,某些事件可能会阻止语句正常完成:

    * break(14.15)、continue(14.16)和 return(14.17)语句会导致控制转移,阻止包含他们的语句的正常完成。

    * 特定表达式的计算可能从 Java 虚拟机(15.6)中抛出异常。显式的 throw(14.18)语句也会产生异常。异常会导致控制转移,阻止语句的正常完成。

如果这种事情发生,则一个或多个语句的执行会在他们的执行的正常模式的所有步骤完成之前被终止;这类语句被称为突然完成。

突然完成总是有一个相关联的原因,其是以下之一:

    * 不带标签的 break

    * 带给定标签的 break

    * 不带标签的 continue

    * 带给定标签的 continue

    * 没有值得 return

    * 带给定值的 return

    * 带给定值的 throw,包括 Java 虚拟机抛出的异常

术语“正常完成”和“突然完成”也适用于表达式(15.6)计算。表达式可以突然完成的唯一理由是抛出异常,要么由于带有给定值(14.18)的 throw,要么由于运行时异常或错误(11(Exceptions),15.6)。

如果语句计算一个表达式,则该表达式的突然完成总是导致该语句的直接突然完成,由于相同的原因。不执行正常执行模式中的所有后续步骤。

除非本章另有说明,子语句的突然完成导致语句本身直接突然完成,由于相同的原因,并且不执行语句正常执行模式中的所有后续步骤。

除非另有说明,一个语句正常完成,如果它计算的所有表达式和它执行的所有子语句都正常完成。

14.2. 块

块是大括号内的语句、局部类声明和局部变量声明语句的序列。

Block:
    { [BlockStatements] }

BlockStatements:
    BlockStatement {BlockStatement}

BlockStatement:
    LocalVariableDeclarationStatement
    ClassDeclaration
    Statement

通过以从第一个到最后一个(从左到右)的顺序执行局部变量声明和其他语句的每个,来执行块。如果这些块语句的所有都正常完成,则块正常完成。如果这些块语句中的任何因任何理由而突然完成,则该块因相同的理由而突然完成。

14.3. 局部类声明

局部类是嵌套类(8(Classes)),其不是任何类的成员,并具有一个名称(6.2,6.7)。

所有的局部类都是内部类(8.1.3).

每个局部类声明语句直接被块(14.2)包含。局部类声明语句可以与块中其他种类的语句自由地混合。

如果局部类声明包含访问修饰符 public、protected 或 private(6.6),或者修饰符 static(8.1.1)中的任何一个,则这是一个编译时错误。

在 6.3 和 6.4 中指定局部类声明的作用于和遮蔽。

avatar

14.4. 局部变量声明语句

局部变量声明语句声明一个或多个局部变量名称。

LocalVariableDeclarationStatement:
    LocalVariableDeclaration ;

LocalVariableDeclaration:
    {VariableModifier} UnannType VariableDeclaratorList

有关 UnannType,请参见 8.3。为了方便,以下来自 4.3、8.4.1 和 8.3 的产生式在这显示:

VariableModifier:
    (one of)
    Annotation final

VariableDeclaratorList:
    VariableDeclarator {, VariableDeclarator}

VariableDeclarator:
    VariableDeclaratorId [= VariableInitializer]

VariableDeclaratorId:
    Identifier [Dims]

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

VariableInitializer:
    Expression
    ArrayInitializer

每个局部变量声明语句直接被块包含。局部变量声明语句可以与块中其他种类的语句自由地混合。

除了局部变量声明语句之外,局部变量声明可以出现在 for 语句(14.14)或 try-with-resources 语句(14.20.3)的头部。这种情况下,以同样的方式执行它,就好像它是局部变量声明语句的一部分一样。

局部变量声明上注解修饰符规则在 9.7.4 和 9.7.5 中指定。

如果 final 作为局部变量声明的修饰符多次出现,则这是一个编译时错误。

14.4.1. 局部变量声明符和类型

局部变量声明中的每个声明符都声明一个局部变量,其名称是声明符中出现的 Identifier。

如果可选的关键字 final 出现在声明的开始处,则被声明的变量是 final 变量(4.12.4)。

如果 UnannType 和 VariableDeclaratorId 中未出现括号对,则局部变量的声明类型由 UnannType 表示,由 10.2 指定。

类型 float 的局部变量总是包含为浮点值集(4.2.3)元素的值;类似地,类型 double 的局部变量总是包含为双精度浮点值集元素的值。即不允许类型 float 的局部变量包含不是浮点值集元素的浮点扩展指数值集元素,也不允许类型 double 的局部变量包含不是双精度浮点值集元素的双精度扩展指数值集元素。

局部变量声明的作用于和遮蔽在 6.3 和 6.4 中指定。

14.4.2. 局部变量声明的执行

局部变量声明语句是一个可执行语句。每次执行时,以从左到右的顺序处理声明符。如果声明符具有初始化器,则初始化器被计算,将它的值赋予该变量。

如果声明符没有初始化器,则该变量的每个引用之前必须有一个该变量的赋值的执行,否则根据 16(Definite Assignment)的规则,发生一个编译时错误。

每个初始化器(第一个除外)被计算,仅当前面的初始化器的计算正常完成。

局部变量声明的执行正常完成,仅当最后一个初始化器的计算正常完成。

如果局部变量声明不包含任何初始化器,则它的执行总是正常完成。

14.5. 语句

Java 编程语言中有许多种语句。大多数对应 C 和 C++ 语句中的语句,但有些是唯一的。

正如 C 和 C++ 中一样,Java 编程语言的 if 语句也遭受“摇摆不定的 else 问题”,以下误导格式化的示例所示:

avatar

此问题是,外部的 if 语句和内部的 if 语句都自信拥有此 else 子句。在这个示例中,有的人可能猜测,程序员想要 else 子句属于外部的 if 语句。

Java 编程语言,像 C、C++ 和他们之前的许多语言一样,都武断地裁定,一个 else 子句属于他尽可能属于的最内部的 if。此规则由以下语句捕获:

Statement:
    StatementWithoutTrailingSubstatement
    LabeledStatement
    IfThenStatement
    IfThenElseStatement
    WhileStatement
    ForStatement

StatementNoShortIf:
    StatementWithoutTrailingSubstatement
    LabeledStatementNoShortIf
    IfThenElseStatementNoShortIf
    WhileStatementNoShortIf
    ForStatementNoShortIf

StatementWithoutTrailingSubstatement:
    Block
    EmptyStatement
    ExpressionStatement
    AssertStatement
    SwitchStatement
    DoStatement
    BreakStatement
    ContinueStatement
    ReturnStatement
    SynchronizedStatement
    ThrowStatement
    TryStatement

以下来自 14.9 的产生式在这显示:

IfThenStatement:
    if ( Expression ) Statement

IfThenElseStatement:
    if ( Expression ) StatementNoShortIf else Statement

IfThenElseStatementNoShortIf:
    if ( Expression ) StatementNoShortIf else StatementNoShortIf

因此,语句从语法上分为两种:在没有 else 子句(“短 if 语句”)的 if 语句中结束的那些,和什么也不做的那些。

只有那些明确不在短 if 语句中结束的语句才可以作为没有 else 子句的 if 语句中的关键字 else 之前的直接子语句出现。

此简单规则防止了“摇摆不定的 else”问题。带有“无短 if”约束的语句的执行行为等价于不具有“无短 if”约束的同种语句的执行行为;这一区别纯粹是为了解决句法上的困难。

14.6. 空语句

空语句不做任何事。

EmptyStatement:
    ;

空语句的执行总是正常完成。

14.7. 带标签的语句

语句可以有标签前缀。

LabeledStatement:
    Identifier : Statement

LabeledStatementNoShortIf:
    Identifier : StatementNotShortIf

Identifier 被声明为直接包含的 Statement 的标签。

与 C 和 C++ 不同,Java 编程语言没有 goto 语句;标识符语句标签用于用于出现在标记语句中任何位置的 break 或 continue 语句(14.15,14.16)。

标记语句的标签的作用域是直接包含的 Statement。

如果标记语句的标签的名字在该标签的作用域中用作另一个标记语句的标签,则这是一个编译时错误。

将相同的标识符用作标签,同时又用作包、类、接口、方法、字段、参数或局部变量的名称,没有任何限制。使用标识符来标记语句并不掩盖(6.4.2)具有相同名称的包、类、接口、方法、字段、参数或局部变量。用作类、接口、方法、字段、局部变量或异常处理器(14.20)参数的标识符,并不会掩盖具有相同名称的语句标签。

通过直接执行包含的 Statement 来执行标记语句。

如果语句被 Identifier 标记,且包含的 Statement 因带有相同 Identifier 的 break 而突然完成,则标记语句正常完成。在 Statement 突然完成的所有其他情况下,标记语句因相同的原因而突然完成。

avatar

14.8. 表达式语句

某些种类的表达式可以通过后跟冒号来用作语句。

ExpressionStatement:
    StatementExpression ;

StatementExpression:
    Assignment
    PreIncrementExpression
    PreDecrementExpression
    PostIncrementExpression
    PostDecrementExpression
    MethodInvocation
ClassInstanceCreationExpression

通过计算表达式来执行表达式语句;如果表达式有值,则值被丢弃。

当且仅当表达式的计算正常完成时,表达式语句的执行才正常完成。

与 C 和 C++ 不同,Java 编程语言只允许某些形式的表达式用作表达式语句。例如,使用方法调用表达式(15.12)是合法的。

avatar

但是使用带括号的表达式(15.8.5)是不合法的:

avatar

注意,Java 编程语言不允许“cast to void” - void 不是类型 - 因此,传统的 C 编写表达式的技巧,例如:

avatar

无法正常工作。另一方面,Java 编程语言允许表达式语句中的所有最有用的表达式,它不需要用作表达式语句的方法调用来调用 void 方法,因此,几乎不需要这样的技巧。如果需要技巧,则可以改用赋值语句(15.26)或局部变量声明语句(14.4)。

14.9. if 语句

if 语句允许语句的条件执行或两个语句的条件选择,执行其中一个或另一个,而不是两个。

IfThenStatement:
    if ( Expression ) Statement

IfThenElseStatement:
    if ( Expression ) StatementNoShortIf else Statement

IfThenElseStatementNoShortIf:
    if ( Expression ) StatementNotShortIf else StatementNoShortIf

Expression 必须是类型 boolean 或 Boolean,否则发生一个编译时错误。

14.9.1. if-then 语句

通过先执行 Expression 来执行 if-then 语句。如果结果是 Boolean 类型,则它受拆箱转换(5.1.8)的影响。

如果 Expression 的计算或随后的拆箱转换(如果有)因某种理由而突然完成,则该 if-then 语句因相同的理由而突然完成。

否则,执行通过基于产生的值做选择来继续:

    * 如果值是 true,则执行包含的 Statement,当且仅当 Statement 的执行正常完成时,该 if-then 语句正常完成。

如果值是 false,则不采取进一步的操作,该 if-then 语句正常完成。

14.9.2. if-then-else 语句

通过先执行 Expression 来执行 if-then-else 语句。如果结果是 Boolean 类型,则它受拆箱转换(5.1.8)的影响。

如果 Expression 的计算或随后的拆箱转换(如果有)因某种理由而突然完成,则该 if-then-else 语句因相同的理由而突然完成。

否则,执行通过基于产生的值做选择来继续:

    * 如果值是 true,则执行第一个包含的 Statement(else 关键字之前的那个),当且仅当该语句的执行正常完成时,此 if-then-else 语句才正常完成。

如果值是 false,则执行第二个包含的 Statement(else 关键字之后的那个),当且仅当该语句的执行正常完成时,此 if-then-else 语句才正常完成。

14.10. assert 语句

断言是包含 boolean 表达式的 assert 语句。断言要么启用,要么禁用。如果断言启用,则该断言的执行导致 boolean 表达式的执行,如果该 boolean 表达式计算为 false,则报告错误。如果断言禁用,则断言的执行不会产生任何影响。

AssertStatement:
    assert Expression ;
    assert Expression : Expression ;

为了简化演示,两种形式的 assert 语句中的第一个 Expression 被称为 Expression1。在第二种形式的 assert 语句中,第二个 Expression 被称为 Expression2。

如果 Expression1 不是 boolean 或 Boolean 类型,则这个一个编译时错误。

在第二种形式的 assert 语句中,如果 Expression2 是 void(15.1),则这是一个编译时错误。

当且仅当宿主系统已确定,词法上包含 assert 语句的顶层类或接口启用断言时,在断言的类或接口已经完成初始化后执行的断言被启用。

顶层类或接口是否启用断言的确定不晚于最早的 i) 顶层类或接口的初始化,和 ii) 嵌套在顶层类或接口中的任何类或接口的初始化。顶层类或接口是否启用断言在确定之后无法更改。

在类或接口完成初始化之前执行的 assert 语句是启用的。

avatar

禁用的 assert 语句什么也不做。特别是,Expression1 和 Expression2(如果存在)都不计算。禁用的 assert 语句总是正常完成。

启用的 assert 语句由第一次计算 Expression1 执行。如果结果类型是 Boolean,则它受拆箱转换(5.1.8)的影响。

如果 Expression1 的计算或随后的拆箱转换(如果有)因某种理由而突然完成,则该 assert 语句因同样的理由而突然完成。

否则,执行通过基于 Expression1 的值做出选择而继续:

    * 如果值是 true,则不采取进一步的操作,assert 语句正常完成。

    * 如果值是 false,则执行行为依赖于 Expression2 是否存在:

        如果 Expression2 存在,则计算它。然后:

            如果计算因某种理由而突然完成,则 assert 语句因同样的理由而突然完成。

            如果计算正常完成,则创建一个“详情消息”是 Expression2 的产生值的 AssertionError 实例。然后:

                如果实例创建因某种理由而突然完成,则 assert 语句因同样的理由而突然完成。

                如果实例创建正常完成,则 assert 语句通过抛出新创建的 AssertionError 对象来突然完成。

        如果 Expression2 不存在,则创建一个没有“详情消息”的 AssertionError 实例。然后:

            如果实例创建因某种理由而突然完成,则 assert 语句因同样的理由而突然完成。

            如果实例创建正常完成,则 assert 语句通过抛出新创建的 AssertionError 实例来突然完成。

通常,在程序开发和测试期间开启断言,并且部署时禁用,以提高性能。

因为断言可能被禁用,所以程序不能假定,断言中包含的表达式会被计算。因此,这些 boolean 表达式通常应该没有副作用。计算此类 boolean 表达式不应该影响在完成计算后可见的任何状态。断言中包含的 boolean 表达式具有副作用是合法的,但通常是不恰当的,因为它可能会导致行为因断言是启用还是禁用而有所不同。

鉴于此,不应该将断言用于 public 方法中的参数检查。参数检查通常是方法协定的一部分,无论是启用还是禁用断言,都必须维护此协定。

使用断言进行参数检查的第二个问题是,错误的参数应该导致适当的运行时异常(例如 IllegalArgumentException、ArrayIndexOutOfBoundsException 或 NullPointerException)。断言失败不会抛出适当的异常。此外,将断言用于 public 方法上的参数检查是合法的,但通常不适当。它的目的是从不捕获 AssertionError,但可以这样做,因此,try 语句规则应该对待出现在 try 块中的断言类似于当前对 throw 语句的处理。

14.11. switch 语句

switch 语句根据表达式的值将控制转移给几个语句中的一个。

SwitchStatement:
    switch ( Expression ) SwitchBlock

SwitchBlock:
    { {SwitchBlockStatementGroup} {SwitchLabel} }

SwitchBlockStatementGroup:
    SwitchLabels BlockStatements

SwitchLabels:
    SwitchLabel {SwitchLabel}

SwitchLabel:
    case ConstantExpression :
    case EnumConstantName :
    default :

EnumConstantName:
    Identifier

Expression 的类型必须是 char、byte、short、int、Character、Byte、Short、Integer、String 或枚举类型(8.9),否则发生编译时错误。

switch 语句的 body 被称为 switch 块。swith 块直接包含的任何语句可以被一个或多个 switch 标签标记,其是 case 或 default 标签。每个 case 标签都有一个 case 常量,其要么是一个常量表达式,要么是一个枚举常量的名称。switch 标签和他们的 case 常量被称为与 switch 语句相关联。

给定一个 switch 语句,以下所有都必须是 true,否则发生编译时错误:

    * 与 swith 语句相关联的每个 case 常量必须与 switch 语句的 Expression(5.2)的类型是赋值兼容的。

    * 如果 switch 语句的 Expression 的类型是枚举类型,则与 switch 语句相关联的每个 case 常量必须是该类型的枚举常量。

    * 与 switch 语句相关联的任意两个 case 常量不能具有相同的值。

    * 与 switch 语句相关联的任意 case 常量都不能是 null。

    * 至多一个 default 标签与 switch 语句相关联。

禁止使用 null 作为 case 常量可防止编写的代码永远不被执行。如果 switch 语句的 Expression 是引用类型,即,String 或装箱的基元类型或枚举类型,则如果 Expression 在运行时计算为 null,将抛出异常。在 Java 编程语言设计者的判断中,这相比悄悄跳过整个 switch 语句或选择执行 default 标签(如果有)之后的语句(如果有),是一个更好的结果。

鼓励(但不是必须的)Java 编译器提供警告,如果枚举值表达式上的 switch 缺少 default 标签,并且缺少一个或多个枚举常量的 case 标签。此类 swtich 将悄悄地什么也不做,如果表达式计算为缺少的常量。

avatar

当执行 switch 语句时,首先执行 Expression。如果 Expression 计算为 null,则抛出一个 NullPointerException,整个 switch 语句因该原因而突然完成。否则,如果结果是引用类型,则它受拆箱转换(5.1.8)的影响。

如果 Expression 的计算或随后的拆箱转换(如果有)因某种理由而突然完成,则该 switch 语句因相同的理由而突然完成。

否则,执行通过比较 Expression 的值和每个 case 常量来继续,有以下选择:

    * 如果 case 常量之一与表达式的值相等,则我们称该 case 标签匹配。switch 块中该匹配的 case 标签之后的所有语句,如果有,按顺序执行。

      如果这些这些语句都正常完成,或如果匹配 case 标签之后没有语句,则整个 switch 语句正常完成。

    * 如果没有相匹配的 case 标签,但有 default 标签,则 switch 块中 default 标签之后的所有语句,如果有,按顺序执行。

      如果这些都正常完成,或如果 default 标签之后没有语句,则整个 switch 语句正常完成。

    * 如果没有匹配的 case 标签,且没有 default 标签,则不采取进一步的操作,switch 语句正常完成。

如果 switch 语句的 body Block 直接包含的任何语句突然完成,按如下处理:

    * 如果 Statement 的执行由于不带标签的 break 而突然完成,则不采取进一步操作,switch 语句正常完成。

    * 如果 Statement 的执行因任何其他理由而突然完成,则 switch 语句因相同的理由而突然完成。

由于 标签的 break 而突然完成的情况由标记的语句(14.7)的一般规则处理。

avatar avatar

14.12. while 语句

while 语句执行一个 Expression,重复一个 Statement 直到 Expression 的值为 false。

WhileStatement:
    while ( Expression ) Statement

WhileStatementNoShortIf:
    while ( Expression ) StatementNoShortIf

Expression 必须是 boolean 或 Boolean 类型,否则发生编译时错误。

首先通过计算 Expression 来执行 while 语句。如果结果是 Boolean 类型,则它受拆箱转换(5.1.8)的影响。

如果 Expression 的计算或随后的拆箱转换(如果有)因某种理由而突然完成,则 while 语句因同样的理由而突然完成。

否则,执行通过根据产生的值做出选择继续:

    * 如果值是 true,则执行包含的 Statement。然后有一个选择:

        如果 Statement 的执行正常完成,则整个 while 语句再次执行,以重新计算 Expresion 为开头。

        如果 Statement 的执行突然完成,请参见 14.12.1。

    * 如果 Expression 的值(可能拆箱)是 false,则不采取进一步操作,while 语句正常完成。

      如果 Expression 的值(可能拆箱)在第一次计算时是 false,则不执行 Statement。

14.12.1. while 语句的突然完成

用以下方式处理包含的 Statement 的突然完成:

    * 如果 Statement 的执行由于不带标签的 break 而突然完成,则不采取进一步操作,while 语句正常完成。

    * 如果 Statement 的执行 由于不带标签的 continue 而突然完成,则再次执行整个 while 语句。

    * 如果 Statement 的执行由于带标签 L 的 continue 而突然完成,则有一个选择:

        如果 while 语句具有标签 L,则再次执行整个 while 语句。

        如果 while 语句不具有标签 L,则 while 语句由于带标签 L 的 continue 而突然完成。

    * 如果 Statement 的执行因任何其他理由而突然完成,则 while 语句因相同的理由而突然完成。

      由于带标签的 break 而突然完成的情况由标记的语句(14.7)的一般规则处理。

14.13. do 语句

do 语句重复地执行一个 Statement 和一个 Expression,直到 Expression 的值为 false。

DoStatement:
    do Statement while (Expression) ;

Expression 必须是 boolean 或 Boolean 类型,否则发生编译时错误。

通过首先执行 Statement 来执行 do 语句。然后有一个选择:

    * 如果 Statement 的执行正常完成,则计算 Expression。如果结果类型是 Boolean,则它受拆箱转换(5.1.8)的影响。

      如果 Expression 的执行或随后的拆箱转换(如果有)因某种理由而突然完成,则 do 语句因相同的理由而突然完成。

      否则,基于产生的值有一个选择:

        如果值是 true,则再次执行整个 do 语句。

        如果值是 false,则不采取进一步操作,do 语句正常完成。

如果 Statement 的执行突然完成,请参见 14.13.1。

执行 do 语句总是执行包含 Statement 至少一次。

14.13.1. do 语句的突然完成

以以下方式处理包含的 Statement 的突然完成:

    * 如果 Statement 的执行由于不带标签的 break 而突然完成,则不采取进一步操作,do 语句正常完成。

    * 如果 Statement 的执行由于不带标签的 continue 而突然完成,则计算 Expression。然后基于产生的值有一个选择:

        如果值是 true,则再次执行整个 do 语句。

        如果值是 false,则不采取进一步操作,do 语句正常完成。

    * 如果 Statement 的执行由于带标签 L 的 continue 而突然完成,则这有一个选择:

        如果 do 语句具有标签 L,则计算 Expression。然后有一个选择:

            如果 Expression 的值是 true,则再次执行整个 do 语句。

            如果 Expression 的值是 false,则不采取进一步操作,do 语句正常完成。

        如果 do 语句不具有标签 L,则 do 语句由于带标签 L 的 continue 而突然完成。

    * 如果 Statement 因任何其他理由而突然完成,则 do 语句因相同的理由而突然完成。

      因带标签的 break 而突然完成的情况按标记的语句(14.7)的一般规则处理。

avatar

14.14. for 语句

for 语句具有两种形式:

    * 基本 for 语句。

    * 增强 for 语句

ForStatement:
    BasicForStatement
    EnhancedForStatement

ForStatementNoShortIf:
    BasicForStatementNoShortIf
    EnhancedForStatementNoShortIf

14.14.1. 基本 for 语句

基本 for 语句执行某些初始化代码,然后重复地执行一个 Expression、一个 Statement 和一些更新代码,直到 Expression 的值为 false。

BasicForStatement:
    for ( [ForInit] ; [Expression] [ForUpdate] ) Statement

BasicForStatementNoShortIf:
    for ( [ForInit] ; [Expression] ; [ForUpdate] ) StatementNoShortIf

ForInit:
    StatementExpressionList
    LocalVariableDeclaration

ForUpdate:
    StatementExpressionList

StatementExpressionList:
    StatementExpression {, StatementExpression}

Expression 必须是 boolean 或 Boolean 类型,否则发生编译时错误。

基本 for 语句的 ForInit 部分中声明的局部变量的作用和遮蔽在 6.3 和 6.4 中指定。

14.14.1.1. for 语句的初始化

首先通过执行 ForInit 代码来执行 for 语句:

    * 如果 ForInit 代码是语句表达式(14.8)列表,则从左到右的顺序执行这些表达式,丢弃他们的值,如果有。

      如果任何表达式的计算因某种理由而突然完成,则 for 语句因相同的理由而突然完成;突然完成的 ForInit 语句表达式右侧的任何语句语句表达式不再计算。

    * 如果 ForInit 代码是局部变量声明(14.4),则执行它就像在块中出现的局部变量声明语句一样。

      如果局部变量声明的执行因某种理由而突然完成,则 for 语句因相同的理由而突然完成。

    * 如果 ForInit 部分不存在,则不采取操作。

14.14.1.2. for 语句的迭代

下一步,运行 for 迭代步骤,如下所示:

    * 如果 Expression 存在,则计算它。如果结果类型是 Boolean,则它受拆箱转换(5.1.8)的影响。

      如果 Expression 的计算或随后的拆箱转换(如果有)突然完成,则 for 语句因相同的理由而突然完成。

      否则,根据 Expression 存在或不存在,以及 Expression 存在时产生的值,做一个选择;看下一个着重的地方。

    * 如果 Expression 不存在,或它存在且从其计算(包括任何可能的拆箱)产生的值是 true,则执行包含的 Statement。然后做一个选择:

        如果 Statement 的执行正常完成,则按顺序执行以下两步:

            1. 首先,如果 ForUpdate 部分存在,则按从左到右的顺序计算该表达式;丢弃他们的值,如果有。如果任何表达式的计算因某种理由而突然完成,则 for 语句因相同的理由而突然完成;突然完成的 ForUpdate 表达式右侧的任何表达式都不再计算。

              如果 ForUpdate 部分不存在,则不采取操作。

            1. 第二,执行另一个 for 迭代步骤。

        如果 Statement 的执行突然完成,请参见 14.14.1.3。

    * 如果 Expression 存在,从其计算(包括任何可能的拆箱)产生的值是 false,则不采取进一步操作,for 语句正常完成。

      如果 Expression 的值(可能拆箱)在第一次计算时是 false,则不执行 Statement。

如果 Expression 不存在,则 for 语句可以正常完成的唯一方式是使用 break 语句。

14.14.1.3. for 语句的突然完成

按一下方式处理包含的 Statement 的突然完成:

    * 如果 Statement 的执行由于不带标签的 break 而突然完成,则不采取进一步操作,for 语句正常完成。

    * 如果 Statement 的执行由于不带标签的 continue 而突然完成,则按顺序执行一下两步:

        1. 首先,如果 ForUpdate 部分存在,则以从左到右的顺序计算表达式,如果有,丢弃他们的值。

          如果 ForUpdate 部分不存在,则不采取操作。

        2. 第二,执行另一个 for 迭代步骤。

    * 如果 Statement 的执行由于带标签 L 的 continue 而突然完成,则这有一个选择:

        如果此 for 语句具有标签 L,则按顺序执行一下两步:

          1. 首先,如果 ForUpdate 部分存在,则以从左到右的顺序计算表达式,如果有,则丢弃他们的值。

            如果 ForUpdate 不存在,则不执行操作。

        如果 for 语句不具有标签 L,则该 for 语句由于带标签 L 的 continue 而突然完成。

    * 如果 Statement 的执行因任何其他理由而突然完成,则 for 语句因相同的理由而突然完成。

      注意,由于带标签的 break 而突然完成的情况按标记的语句(14.7)的一般规则处理。

14.14.2. 增强 for 语句

增强 for 语句具有如下形式:

EnhancedForStatement:
    for ( {VariableModifier} UnannType VariableDeclaratorId : Expression ) Statement

EnhancedForStatementNoShortIf:
    for ( {VariableModifier} UnannType VariableDeclaratorId " Expression ) StatementNoShortIf

有关 UnannType,请参见 8.3。一下来自 4.3、8.4.1 和 8.3 的产生式在这显示:

VariableModifier:
    (one of)
    Annotation final

VariableDeclaratorId:
    Identifier [Dims]

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

Expression 的类型必须是 Iterable 或数组类型(10.1),否则发生编译时错误。

如果 UnannType 和 VariableDeclaratorId 中没有出现括号对,增强 for 语句头中的局部变量的声明类型用 UnannType 表示,用 10.2 中其他方式指定。

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

增强 for 语句的含义通过到基本 for 语句的转换来给定,如下所示:

    * 如果 Expression 的类型是 Iterable 的子类,则按如下转换:

      如果 Expression 的类型是某个类型参数 X 的 Iterable 的子类型,则让 I 是类型 java.util.Iterator;否则,让 I 是原始类型 java.util.Iterator。

      增强 for 语句等价于如下形式的基本 for 语句:

avatar

      #i 是自动生成的不同于增强 for 语句出现的点处的作用域中的任何其他标识符(自动生成或其他方式)的标识符。

      如果增强 for 语句的头中的局部变量的声明类型是引用类型,则 TargetType 是该声明类型;否则,TargetType 是 I 的类型参数的捕获转换(5.1.10)的上边界,或 Object 如果 I 是原始的。

avatar

    * 否则,Expression 必须具有数组类型,T[]。

      让 L1 ... Lm 是直接在增强 for 语句前面的标签序列(可能是空的)。

      增强 for 语句等价于如下形式的基本 for 语句:

avatar

      #a 和 #i 是自动生成的不同于在增强 for 语句出现的点处作用域中的任何其他标识符(自动生成或其他方式)的标识符。

      TargetType 是增强 for 语句的头中的局部变量的声明类型。

avatar

avatar

14.15. break 语句

break 语句将控制转移到封闭语句之外。

BreakStatement:
    break {Identifier} ;

不带标签的 break 语句试图将控制转移给直接封闭方法或初始化器的最内部的封闭 switch、while、do 或 for 语句;然后此语句,其被称为 break 目标,直接正常完成。

准确地说,不带标签的 break 语句总是异常完成,原因是不带标签的 break。

如果直接封闭方法、构造器或初始化器中没有 switch、while、do 或 for 语句包含 break 语句,则发生编译时错误。

带标签 Identifier 的 break 语句试图将控制转移给具有相同 Identifier 作为其标签的封闭标记的语句(14.7);然后此语句,其被称为 break 目标,直接正常完成。在这总情况下,break 目标不必是 switch、while、do 或 for 语句。

准确地说,带标签 Identifier 的 break 语句总是突然完成,原因是带标签 Identifier 的 break。

break 语句必须在直接封闭方法、构造器、初始化器或 lambda body 中引用一个标签。没有非局部跳跃。如果直接封闭方法、构造器、初始化器或 lambda body 中没有 Identifier 作为其标签的标记语句包含 break 语句,则发生编译时错误。

然后,可以看到,break 语句总是突然完成。

前面的描述说,“试图转移控制”而不是“转移控制”,因为如果 break 目标,其 try 块或 catch 子句包含 break 语句,中有任何 try 语句(14.20),然后以从最内部到最外部的顺序执行这些 try 语句的任何 finally 子句,在控制转移到 break 目标之前。finally 子句的突然完成可能会中断 break 语句启动的控制转移。

avatar avatar avatar

14.16. continue 语句

continue 语句仅可以出现在 while、do 或 for 语句中;这三种语句被称为迭代语句。控制传递到迭代语句的循环延续点。

ContinueStatement:
    continue {Identifier} ;

不带标签的 continue 语句试图将控制转移给直接封闭方法、构造器或初始化器的最内部的封闭 while、do 或 for 语句,这个语句,其被称为 continue 目标,然后直接结束当前迭代,开始一个新的。

准确地说,此类 continue 语句总是突然完成,理由是不带标签的 continue。

如果没有直接封闭方法、构造器或初始化器的 while、do 或 for 语句包含 continue 语句,否则发生编译时错误。

带标签 Identifier 的 continue 语句试图将控制转移给封闭标记的具有相同的 Identifier 作为其标签的语句(14.7);然后该语句,其被称为 continue 目标,直接结束当前的迭代,开始一个新的。

准确地说,带标签 Identifier 的 continue 语句总是突然完成,理由是 带标签 Identifier 的 continue。

continue 目标必须是 while、do 或 for,否则发生编译时错误。

continue 语句必须引用直接封闭方法、构造器、初始化器或 lambda body 中的标签。没有非局部跳转。如果在直接封闭方法、构造器、初始化器或 lambda body 中没有将 Identifier 作为其标签的标记的语句包含 continue 语句,则发生编译时错误。

然后,可以看到,continue 语句总是突然完成。

请参见 while 语句(14.12)、do 语句(14.13)和 for 语句(14.14)有关因 continue 而突然完成的处理的讨论的描述。

前面的描述说,“试图转移控制”而不是“转移控制”,因为如果在 continue 目标中有任何 try 语句(14.20),该 continue 目标的 try 块或 catch 子句包含 continue 语句,则执行这些 try 语句的所有 finally 子句,以从最内部到最外部的顺序,在控制转移给 continue 目标之前。finally 子句的突然完成可能会中断由 continue 语句启动的控制转移。

14.17. return 语句

return 语句将控制返回给方法(8.4,15.12)或构造器(8.8,15.9)的调用者。

ReturnStatement:
    return [Expression] ;

return 语句被包含在最里面的其 body 封闭该 return 语句的构造器、方法、初始化器或 lambda 表达式中。

如果 return 被包含在实例初始化器或 static 初始化器(8.6,8.7)中,则这是一个编译时错误。

不带 Expression 的 return 语句必须被包含在以下之一中,否则发生编译时错误:

    * 使用关键字 void 声明的不返回值(8.4.5)的方法

    * 构造器(8.8.7)

    * lambda 表达式(15.27)

不带 Expression 的 return 语句试图将控制转移给包含它的方法、构造器或 lambda body 的调用者。准确地说,不带 Expression 的 return 语句总是突然完成,理由是不带值的 return。

带有 Expression 的 return 语句必须被包含在以下之一中,否则发生编译时错误:

    * 声明返回值的方法

    * lambda 表达式

Expression 必须表示一个变量或值,否则发生编译时错误。

当带 Expression 的 return 语句出现在方法声明中时,该 Expression 必须可被赋值为声明的方法返回类型,否则发生编译时错误。

带 Expression 的 return 语句试图将控制转移给包含它的方法或 lambda body 的调用者;Expression 的值成为方法调用的值。更精确地说,此类 return 语句的执行首先计算 Expression。如果 Expression 的计算因某种理由而突然完成,则该 return 语句因相同的理由而突然完成。如果 Expressioin 的计算正常完成,产生一个值 V,则该 return 语句突然完成,理由是值 V 的返回。

如果表达式是 float 类型,不是 FP-strict(15.4)的,则该值既可以是浮点值集的元素,也可以是浮点扩展指数值集(4.2.3)的元素。如果表达式是 double 类型,不是 FP-strict 的,则该值既可以是双精度值集的元素,也可以是双精度扩展指数值集的元素。

然后,可以看到,return 语句总是突然完成。

前面的描述说,“试图将控制转移”而不是“转移控制”,因为如果其 try 块或 catch 子句包含该 return 语句的方法或构造器内的任何 try 语句(14.20)存在,则这些 try 语句的任何 finally 子句将会被执行,以从最里面到最外面的顺序,在将控制转移给方法或构造器的调用者之前。finally 子句的突然完成可以中断由 return 语句启动的控制转移。

14.18. throw 语句

throw 语句导致异常(11(Exceptions))被抛出。结果是一个立即的控制转移(11.3),其可能离开多个语句和多个构造器、实例初始化器、static 初始化器和字段初始化器计算和方法调用,直到一个 try 语句(14.20)被找到,其捕获了抛出的值。如果没找到这种 try 语句,则执行该 throw 的线程(17(Threads and Locks))的执行被终止,在该线程所属的线程组的 uncaughtException 方法的调用之后。

ThrowStatement:
    throw Expressioin ;

throw 语句中的 Expressioin 要么表示一个可赋值为 Throwable 类型的变量或引用类型值,要么表示 null 引用,否则发生编译时错误。

Expression 的引用类型始终是非参数化(因为 Throwable 的子类不能是泛型的(8.1.2))的类类型(因为没有接口类型可赋值为 Throwable)。

至少一下三个条件之一必须为 true,否则发生编译时错误:

    * Expression 的类型是未检查异常类(11.1.1)或 null 类型(4.1).

    * throw 语句被包含在 try 语句(14.20)的 try 块中,且不是该 try 语句可以抛出 Expression 类型的异常的情况。(在这种情况下,我们称,抛出的值被 try 语句捕获。)

    * throw 语句被包含在方法或构造器声明中,Expression 的类型可赋值为声明的 throws 子句(8.4.6,8.8.5)中列举的至少一个类型。

throw 语句可以抛出的异常类型在 11.2.2 中指定。

throw 语句首先计算 Expression。然后:

    * 如果 Expression 的计算因某种理由而突然完成,则 throw 因相同的理由而突然完成。

    * 如果 Expression 的计算正常完成,产生一个非 null 值 V,则 throw 突然完成,理由是带值 V 的 throw。

    * 如果 Expression 的计算正常完成,产生一个 null 值,则创建一个类 NullPointerException 的实例 V',代替 null 将其抛出。然后 throw 语句突然完成,理由是带值 V' 的 throw。

然后,可以看到,throw 语句总是突然完成。

如果有任何封闭 try 语句(14.20),其 try 块包含 throw 语句,则这些 try 语句的任何 finally 子句都将执行,因为控制将向外转移,直到抛出的值被捕获。请注意,finally 子句的突然完成可能会中断由 throw 语句启动的控制转移。

如果 throw 语句被包含在方法声明或 lambda 表达式中,但它的值不被某个包含它的 try 语句捕获,则方法的调用由于该 throw 而突然完成。

如果 throw 语句被包含在构造器声明中,但它的值不被某个包含它的 try 语句捕获,则调用此构造器的类实例创建表达式将由于该 throw(15.9.4)而突然完成。

如果 throw 语句被包含在 static 初始化器(8.7)中,则编译时检查(11.2.3)确保,要么它的值总是未检查异常,要么它的值总是被某个包含它的 try 语句捕获。如果在运行时,尽管进行了此检查,但某个包含该 throw 语句的 try 语句不捕获它的值,则如果它是类 Error 或其子类之一的实例,重新抛出该值;否则,它被包装在一个 ExceptionInInitializerError 对象中,然后被抛出(12.4.2)。

如果 throw 语句被包含在实例初始化器(8.6)中,则编译时检查(11.2.3)确保,要么它的值总是未检查异常,要么它的值总是被某个包含它的 try 语句捕获,或者被抛出的异常(或其父类之一)的类型在类的每个构造器的 throws 子句中出现。

按照约定,用户声明的可抛出的类型通常应声明为类 Exception 的子类,其是类 Throwable(11.1.1)的子类。

14.19. synchronized 语句

synchronized 语句代表正在执行的线程获取互斥锁(17.1),执行一个块,然后释放该锁。在执行线程拥有锁的期间,其他线程不能获取该锁。

SynchronizedStatement:
    synchronized ( Expression ) Block

Expression 的类型必须是引用类型,否则发生编译时错误。

同过首先执行 Expression 来执行 synchronized 语句。然后:

    * 如果 Expression 的计算因某种理由而突然完成,则 synchronized 语句因相同的理由而突然完成。

    * 否则,如果 Expression 的值是 null,抛出一个 NullPointerException。

    * 否则,让 Expression 的非 null 值是 V。执行线程锁定与 V 相关联的管程。然后执行 Block,然后有一个选择:

        如果 Block 的执行正常完成,则解锁管程,synchronized 语句正常完成。

        如果 Block 的执行因某种理由而突然完成,则解锁管程,synchronized 语句因相同的理由而突然完成。

由 synchronized 语句获取的锁与由 synchronized 方法(8.4.3.6)隐式获取的锁相同(都是同一种事物的意思)。单个线程可以多次获取同一个锁。

获取与一个对象相关联的锁本身并不防止其他线程访问该对象的字段,或在该对象上调用非 synchronized 方法。其他线程也可以以常规方式使用 synchronized 方法或 synchronized 语句来实现互斥。

avatar

14.20. try 语句

try 语句执行一个块。如果一个值被抛出,try 语句有一个或多个可以捕获它的 catch 子句,则控制将转移给第一个这样的 catch 子句。如果 try 语句有一个 finally 子句,则执行另一个代码块,不管 try 块是正常完成还是突然完成,也不管一个 catch 子句是否是第一个指定的控制。

TryStatement:
    try Block Catches
    try Block [Catches] Finally
    TryWithResourcesStatement

Catches:
    CatchClause {CatchClause}

CatchCaluse:
    catch ( CatchFormalParamenter ) Block

CatchFormParamenter:
    {VariableModifier} CatchType VariableDeclaratorId

CatchType:
    UnannClassType {| ClassType}

Finally:
    finally Block

有关 UnannClassType,请参见 8.3。为了方便,以下来自 4.3、8.3 和 8.4.1 的产生式在这显示:

VariableModifier:
    (one of)
    Annotation final

VariableDeclaratorId:
    Identifier [Dims]

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

直接在关键字 try 之后的 Block 被称为 try 语句的 try 块。

直接在关键字 finally 之后的 Block 被称为 try 语句的 finally 块。

try 语句可以有 catch 子句,也被成为异常处理器。

catch 子句仅声明一个参数,这个参数被称为异常参数。

如果 final 作为异常参数声明的修饰符多次出现,则这是一个编译时错误。

异常参数的作用于和遮蔽在 6.3 和 6.4 中指定。

一个异常参数可以将其类型表示为单个类类型,或两个或多个类类型(被称为替代项)的联合。联合的替代项在句法上用 | 分隔。

其异常参数表示为单个类类型的 catch 子句被称为单 catch 子句。

其异常参数表示为类型的联合的 catch 子句被称为多 catch 子句。

在异常参数类型的表示中使用的每个类类型必须是类 Throwable 或 Throwable 的子类,否则发生编译时错误。

如果在异常参数类型的表示中使用类型变量,则这是一个编译时错误。

如果类型的联合包含两个替代项 Di 和 Dj(i ≠ j),其中 Di 是 Dj 的子类型(4.10.2),则这是一个编译时错误。

用单个类类型表示其类型的异常参数的声明类型是该类型。

将其类型表示为替代项 D1 | D2 | ... | Dn 的异常参数的声明类型是 lub(D1, D2, ..., Dn)。

多 catch 子句的异常参数被隐式声明为 final,如果它未被显式声明为 final。

如果隐式或显式被声明为 final 的异常参数被分配给 catch 子句的 body 中,则这是一个编译时错误。

单 catch 子句的异常参数从不隐式声明为 final,但它可以显式声明为 final 或实际上是 final(4.12.4)。

隐式 final 异常参数是由于其声明而是 final,而实际上 final 异常参数是(因为它是)由于它被如何使用而是 final。多 catch 子句的异常参数是隐式声明的 final,因此从不作为赋值操作符的左侧操作数出现,但它不被视为实际 final。

如果一个异常参数是实际上 final(在单 catch 子句中)或隐式 final(在多 catch 子句中),则向其声明添加一个显式 final 修饰符不会引入任何编译时错误。另一方面,如果单 catch 子句的异常参数是显式声明 final,则移除 final 修饰符可能会引入编译时错误,因为异常参数,现在被视为实际上 final,不能再被该 catch 子句的 body 中的匿名和局部类声明引用。如果没有编译时错误,则可以进一步更改程序,以便在 catch 子句的 body 中重新对异常参数赋值,因此将不再被视为实际上 final。

try 语句可以抛出的异常类型在 11.2.2 中指定。

由 try 语句的 try 块抛出的异常和由 try 语句的 catch 子句(如果有)捕获的异常之间的关系在 11.2.3 中指定。

异常处理器的顺序被视为从左到右:最早可能接受异常的 catch 子句,将被抛出的异常对象作为其参数接收,如 11.3 所指定。

avatar

finally 子句确保,在 try 块和可能执行的任何 catch 块之后执行 finally 块,不管控制如何离开 try 块或 catch 块。finally 块的处理相当复杂,因此带 finally 块和不带 finally 块的 try 语句的两种情况分开描述(14.20.1,14.20.2)。

允许 try 语句省略 catch 子句和 finally 子句,如果它是 try-with-resources 语句(14.20.3).

14.20.1. try-cathc 的执行

首先通过执行 try 块来执行不带 finally 块的 try 语句。然后有一个选择:

    * 如果 try 块的执行正常完成,则不采取进一步操作,try 语句正常完成。

    * 如果 try 语句的执行由于值 V 的抛出而突然完成,则有一个选择:

        如果 V 的运行时类型与(5.2)try 语句的任何 catch 子句的可捕获的异常类是赋值兼容的,则第一个(最左侧)这种 catch 子句被选定。值 V 被赋值给选定的 catch 子句的参数,执行该 catch 子句的 Block,然后有一个选择:

            如果该块正常完成,则 try 语句正常完成。

            如果该块因任何理由而突然完成,则 try 语句因相同的理由而突然完成。

        如果 V 的运行时类型与 try 语句的任何 catch 子句的可捕获的异常类都不是赋值兼容的,则 try 语句由于值 V 的抛出而突然完成。

    * 如果 try 块的执行因任何其他理由而突然完成,则 try 语句因相同的理由而突然完成。

avatar

14.20.2. try-finally 和 try-catch-finally 的执行

首先通过执行 try 块来执行带 finally 块的 try 语句。然后有一个选择:

    * 如果 try 块的执行正常完成,则执行 finally 块,然后有一个选择:

        如果 finally 块正常完成,则 try 语句正常完成。

        如果 finally 块因理由 S 而突然完成,则 try 语句因理由 S 而突然完成。

    * 如果 try 块的执行由于值 V 的抛出而突然完成,则有一个选择:

        如果 V 的运行时类型与 try 语句的任何 catch 子句的可捕获的异常类是赋值兼容的,则第一个(最左侧)这种 catch 子句被选定。值 V 被赋值给选定的 catch 子句的参数,执行该 catch 子句的 Block。然后有一个选择:

            如果 catch 块正常完成,则执行 finally 块。然后有一个选择:

                如果 finally 块正常完成,则 try 语句正常完成。

                如果 finally 块因任何理由而突然完成,则 try 语句因相同的理由而突然完成。

            如果 catch 块因理由 R 而突然完成,则执行 finally 块。然后有一个选择:

                如果 finally 块正常完成,则 try 语句因理由 R 而突然完成。

                如果 finally 块因理由 S 而突然完成,则 try 语句因理由 S(理由 R 被丢弃)而突然完成。

        如果 V 的运行时类型与 try 语句的任何 catch 子句的可捕获的异常类都不是赋值兼容的,则执行 finally 块。然后有一个选择:

            如果 finally 块正常完成,则 try 语句由于值 V 的抛出而突然完成。

            如果 finally 块因理由 S 而突然完成,则 try 语句因理由 S 而突然完成(值 V 的抛出被丢弃和遗忘)。

    * 如果 try 块的执行因任何其他理由 R 而突然完成,则执行 finally 块,然后有一个选择:

        如果 finally 块正常完成,则 try 语句因理由 R 而突然完成。

        如果 finally 块因理由 S 而突然完成,则 try 语句因理由 S 而突然完成(理由 R 被丢弃)。

avatar

14.20.3. try-with-resources

try-with-resoures 语句用在 try 块执行之前初始化的变量作为参数,并自动关闭,以初始化他们时的相反顺序,在 try 块执行之后。当自动关闭资源时,catch 子句和 finally 子句通常是不必要的。

TryWithResourcesStatement:
     try ResourceSecification Block [Catches] [Finally]

ResourceSpecification:
     ( ResourceList [;] )

ResourceList:
     Resource {; Resource}

Resource:
     {VariableModifier} UnannType VariableDeclaratorId = Expression

有关 UnannType 请参见 8.3。为了方便,以下来自 4.3 、8.3 和 8.4.1 的产生式在这显示:

VariableModifier:
     (one of)
     Annotation final

VariableDeclaratorId:
     Identifier [Dims]

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

资源说明声明一个或多个具有初始化器表达式的局部变量来充当 try 语句的资源。

资源说明声明两个具有相同名称的变量,是一个编译时错误。

如果 final 作为在资源说明中声明的每个变量的修饰符多次出现,则这是一个编译时错误。

在资源说明中声明的变量被隐式声明为 final(4.12.4),如果它未被显式声明为 final。

在资源说明中声明的变量的类型必须是 AutoCloseable 的子类型,否则发生编译时错误。

在资源说明中声明的变量的作用于和遮蔽在 6.3 和 6.4 中指定。

以从左到右的顺序初始化资源。如果资源初始化失败(即,它的初始化器表达式抛出异常),则关闭到目前为止由 try-with-resources 语句初始化的所有资源。如果所有资源初始化成功,则正常执行 try 块,然后关闭 try-with-resources 语句的所有非 null 资源。

以与初始化相反的顺序关闭资源。仅当初始化为非 null 值时,关闭资源。来自资源关闭的异常不阻止其他资源的关闭。如果以前由初始化器、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 语句的含义是:

avatar

由以下到局部变量声明和 try-catch-finally 语句的转换给定:

avatar

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

#t、#primaryExc 和 #suppressedExc 是自动生成的不同于其他在 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 语句中:

    * 如果资源的初始化由于值 V 的抛出而突然完成,则 try-with-resources 语句由于值 V 的抛出而突然完成。

    * 如果资源的初始化正常完成,try 块由于值 V 的抛出而突然完成,则:

        如果资源的自动关闭正常完成,则 try-with-resources 语句由于值 V 的抛出而突然完成。

        如果资源的自动关闭由于值 V2 的抛出而突然完成,则 try-with-resources 语句由于具有添加到 V 的被抑制异常列表的 V2 的值 V 的抛出而突然完成。

    * 如果资源的初始化正常完成,try 块正常完成,资源de自动关闭由于值 V 的抛出而突然完成,则 try-with-resources 语句由于值 V 的抛出而突然完成。

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

    * 如果资源的初始化由于值 V 的抛出而突然完成,则:

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

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

    * 如果资源的初始化正常完成,try 块由于值 V 的抛出而突然完成,则:

        如果所有初始化的资源的自动关闭正常完成,则 try-with-resources 语句由于值 V 的抛出而突然完成。

        如果一个或多个初始化的资源的自动关闭由于值 V1...Vn 的抛出而突然完成,则 try-with-resources 语句由于具有任何剩余的添加到 V 的被抑制异常列表的值 V1...Vn 的值 V 的抛出而突然完成。

    * 如果每个资源的初始化正常完成,try 块正常完成,则:

        如果某个初始化的资源的自动关闭由于值 V 的抛出而突然完成,其他所有初始化的资源的自动关闭正常完成,则 try-with-resources 语句由于值 V 的抛出而突然完成。

        如果多个初始化的资源的自动关闭由于值 V1...Vn 的抛出而突然完成,则 try-with-resources 语句由于具有任何剩余的添加到 V1 的被抑制异常列表的值 V2...Vn 的值 V1 的抛出而突然完成(其中 V1 是来自最右侧的关闭失败的异常,Vn 是来自最左侧的关闭失败的异常)。

14.20.3.2. 扩展的 try-with-resources

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

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

avatar

由以下到在 try-catch 或 try-finally 或 try-catch-finally 语句里面嵌套的基本 try-with-resources 语句的转换给定:

avatar

转换的效果是将资源说明放到 try 语句的里面。这允许扩展的 try-with-resources 语句的 catch 子句捕获由于任何资源的自动初始化或关闭导致的异常。

此外,在执行 finally 块时,所有都将被关闭(或试图关闭),以符合 finally 关键字的意图。

14.21. 不可达的语句

如果语句因为不可达而无法执行,则这是一个编译时错误。

本节专门讨论“可到达”一词的精确解释。这个理念是,从包含语句的构造器、方法、实例初始化器或 static 初始化器的开始到语句本身,必须有可能的执行路径。这一分析考虑了语句结构。除了其条件表达式具有常量值 true 的 while、do 和 for 语句的特殊处理之外,在流分析中不考虑表达式的值。

avatar

本节中的规则定义两个技术术语:

    * 一个语句是否可达

    * 一个语句是否可以正常完成

这的定义允许语句正常完成,仅当它可达时。

为了简化规则的描述,自定义的缩写“iff”用于表示“当且仅当”。

一个可达的 break 语句离开一个语句,如果,在 break 目标中,要么没有其 try 块包含该 break 语句的 try 语句,要么有其 try 块包含该 break 语句的 try 语句,且这些 try 语句的所有 finally 子句都可以正常完成。

此定义基于 14.15 中“试图转移控制”的逻辑。

一个 continue 语句继续一个 do 语句,如果,在 do 语句中,要么没有其 try 块包含该 continue 语句的 try 语句,要么有其 try 块包含该 continue 语句的 try 语句,且这些 try 语句的所有 finally 子句都可以正常完成。

规则如下:

    * 其是构造器、方法、实例初始化器或 static 初始化器的 body 的块可到达。

    * 其不是 switch 块的空块可以正常完成,当且仅当它可达时。

      其不是 switch 块的非空块可以正常完成,当且仅当其中的最后一个语句可以正常完成。

      不是 switch 块的非空块中的第一个语句是可达的,当且仅当该是可达的。

      不是 switch 块的非空块中的每个其他语句 S 是可达的,当且仅当在 S 前面的语句可以正常完成。

    * 局部类声明语句可以正常完成,当且仅当它是可达的。

    * 局部变量声明语句可以正常完成,当且仅当它是可达的。

    * 空语句可以正常完成,当且仅当它是可达的。

    * 标记的语句可以正常完成,如果以下至少一个为 true:

        包含的语句可以正常完成。

        有一个可达的 break 语句,其离开标记的语句。

      包含的语句是可达的,当且仅当标记的语句是可达的。

    * 表达式语句可以正常完成,当且仅当它是可达的。

    * if-the 语句可以正常完成,当且仅当它是可达的。

      then-statement 是可达的,当且仅当 if-then 语句是可达的。

      if-then-else 语句可以正常完成,当且仅当 then-statement 可以正常完成,或 else-statement 可以正常完成。

      then-statement 是可达的,当且仅当 if-then-else 语句是可达的。

      else-statement 是可达的,当且仅当 if-then-else 语句是可达的。

      if 语句的处理,不管它是否有 else 部分,相当不寻常。本节末尾给出了基本原理。

    * assert 语句可以正常完成,当且仅当它是可达的。

    * switch 语句可以正常完成,当且仅当以下至少一个为 true:

        switch 块是空的,或仅包含 switch 标签。

        switch 块中的最后一个语句可以正常完成。

        在最后一个 switch 块语句组之后有至少一个 switch 标签。

        switch 块不包含 default 标签。

        有一个可达的 break 语句,其离开该 switch 语句。

    * switch 块是可达的,当且仅当它的 switch 语句是可达的。

    * switch 块中的语句是可达的,当且仅当它的 switch 语句是可达的,同时以下至少一个为 true:

        它带有一个 case 或 default 标签。

        switch 块中在它前面有一个语句,该前面的语句可以正常完成。

    * while 可以正常完成,当且仅当以下至少一个为 true:

        while 语句是可达的,条件表达式不是值为 true 的常量表达式(15.28).

        有一个可达的 break 语句,其离开该 while 语句。

      包含的语句是可达的,当且仅当 while 是可达的,条件表达式不是值为 false 的常量表达式。

    * do 语句可以正常完成,当且仅当以下至少一个为 true:

        包含的语句可以正常完成,条件表达式不是值为 true 的常量表达式(15.28)。

        do 语句包含一个可达的不带标签的 continue 语句,该 do 语句是最内层的包含该 continue 语句的 while、do 或 for 语句,该 continue 语句继续该 do 语句,条件表达式不是值为 true 的常量表达式。

        do 语句包含一个可达的带标签 L 的 continue 语句,该 do 语句具有标签 L,continue 语句继续该 do 语句,条件表达式不是值为 true 的常量表达式。

        有一个可达的 break 语句,其离开该 do 语句。

      包含的语句是可达的,当且仅当该 do 语句是可达的。

    * 基本 for 语句可以正常完成,当且仅当以下至少一个为 true:

        for 语句是可达的,有一个条件表达式,该条件表达式不是值为 true 的常量表达式(15.28).

        有一个可达的 break 语句,其离开 for 语句。

      包含的语句是可达的,当且仅当该 for 语句是可达的,条件表达式不是值为 false 的常量表达式。

    * 增强的 for 语句可以正常完成,当且仅当它是可达的。

    * break、continue、return 或 throw 语句无法正常完成。

    * synchronized 语句可以正常完成,当且仅当包含的语句可以正常完成。

      包含的语句是可达的,当且仅当该 synchronized 语句是可达的。

    * try 语句可以正常完成,当且仅当以下两者都是 true:

        try 块可以正常完成,或任何 catch 块可以正常完成。

        如果 try 语句具有一个 finally 块,则该 finally 块可以正常完成。

    * try 块是可达的,当且仅当 try 语句是可达的。

    * catch 块 C 是可达的,当且仅当以下两者都是 true:

        要么 C 的参数的类型是未检查的异常类型或 Exception 或 Exceptino 的父类,要么 try 块中的某个表达式或 throw 语句是可达的,且可以抛出已检查异常,其类型可赋给 C 的参数类型。(表达式是可达的,当且仅当最内层的包含它的语句是可达的。)

        有关表达式的正常和突然完成,请参见 15.6。

        在 try 语句中没有更早的 catch 块 A,以致于 C 的参数类型与 A 的参数类型相同,或是 A 的参数类型的子类。

    * catch 块的 Block 是可达的,当且仅当 catch 块是可达的。

    * 如果 finally 块存在,则当且仅当 try 语句可达时,该 finally 块才可达。

人们可能希望用以下方式处理 if 语句:

    * if-then 语句可以正常完成,当且仅当以下至少一个为 true:

        if-then 语句是可达的,条件表达式不是值为 true 的常量表达式。

        then-statement 可以正常完成。

      then-statement 是可达的,当且仅当 if-then 语句是可达的,且条件表达式不是值为 false 的常量表达式。

    * if-then-else 语句可以正常完成,当且仅当 then-statement 可以正常完成,或 else-statement 可以正常完成。

      then-statement 是可达的,当且仅当 if-then-else 语句是可达的,且条件表达式不是值为 false 的常量表达式。

      else-statement 是可达的,当且仅当 if-then-else 语句是可达的,且条件表达式不是值为 true 的常量表达式。

这种方法与其他控制结构的处理一致。但是,为了允许方便地将 if 语句用于“条件编译”目的,实际规则会有所不同。

例如,下面的语句导致编译时错误:

avatar

因为语句 x=3; 不可达;但是表面上类似的情况:

avatar

不会导致编译时错误。优化编译器可以知道,从不会执行语句 x=3;,可以选择从生成的类文件中省略该语句的代码,但语句 x=3; 在此处指定的技术意义上不被视为“不可达”。

这种不同处理的基本原理是允许程序员定义“标志变量”,例如:

avatar

然后这样写代码:

avatar

其理念是,应该可以将 DEBUG 的值从 false 更改为 true 或从 true 更改为 false,然后正确地编译代码,而不对程序文本进行其他更改。

“条件编译”功能与二进制兼容性(13(Binary Compatibility))没有关系。如果编译一组使用这种“标志”变量的类,条件代码被省略,则以后只分发包含该标志定义的类或接口的新版本就不够了。使用该标志的类将不会看到它的新值,因此他们的行为可能会令人吃惊,但不会发生 LinkageError。因此,标志值得更改与预先存在的二进制是二进制兼容的,但行为上是不兼容的。