目次
プログラムの実行順序は、文によって制御されます。この文は、その効果のために実行され、値を持ちません。
文の中には、構造の一部として他の文が含まれるものがあります。このような文以外は、文のサブ文です。 ステートメント S immediately containsステートメント Uは、Sに Tが含まれ、Tに Uが含まれるように、Sおよび Uと異なるステートメント Tが存在しない場合に使用します。 同じように、一部の文には、その構造の一部として式(§15 (Expressions))が含まれています。
この章の最初のセクションでは、文の正常完了と突然完了の違いについて説明します(§14.1)。 残りのほとんどのセクションでは、さまざまな種類のステートメントについて説明し、それらの通常の動作と突然の完了の特別な処理の両方について詳しく説明します。
ブロックは、最初に説明されています(§14.2)。これは、文が許可されていない特定の場所に表示される可能性があり、ある種類の文である局所変数宣言文(§14.4.2)がブロックによってすぐに含まれている必要があるためです。 ローカル・クラスおよびインタフェース宣言(§14.3)は文ではなく、ブロックによってすぐに含まれる必要があります。
次に、一般的な「dangling else」問題を回避する文法的な操作(§14.5)について説明します。
すべての文は、特定の技術的意味(§14.22)で到達可能である必要があります。
セクション14.23-14.29は、将来新しい種類の文を導入するために未使用です。
この章の最後の節(§14.30)では、局所変数を条件付きで宣言および初期化するために文および式内で使用される patternsについて説明します。 パターンは、1つの値(オブジェクトなど)を、変数宣言で示される1つ以上の他の値から構成する方法の簡潔な説明を示します。 パターン・マッチングは、ある値から1つ以上の値を分解するかのように抽出し、抽出された値を使用してパターンで宣言された変数を初期化します。
すべての文には、特定の計算ステップが実行される通常の実行モードがあります。 次の各項では、各種類の文に対する通常の実行モードについて説明します。
説明どおりにすべてのステップが実行され、突然の完了が示されない場合、文は正常に完了と呼ばれます。 ただし、特定のイベントによって、ステートメントが正常に完了できない場合があります。
このようなイベントが発生した場合、1つ以上の文の実行は、その通常モードの実行のすべてのステップが完了する前に終了する可能性があります。このような文は突然完了と呼ばれます。
突然の完了には常に、次のいずれかの理由が関連付けられます。
ラベルなしのbreak
指定されたラベルを持つbreak
ラベルなしのcontinue
指定されたラベルを持つcontinue
値のないreturn
指定された値を持つreturn
Java Virtual Machineによってスローされた例外を含む、指定された値を持つthrow
指定された値を持つyield
「完全通常」と「完全突然」という用語は、式の評価にも適用されます(§15.6)。 式が突然完了できる唯一の理由は、指定された値を持つthrow (§14.18)または実行時例外またはエラー(§11 (例外)、§15.6)のいずれかが原因で例外がスローされることです。
文が式を評価する場合、式が突然完了すると、同じ理由で文の即時完了が常に発生します。 通常モードの実行では、後続のステップはすべて実行されません。
この章で特に指定されていないかぎり、サブステートメントの突然完了によって、同じ理由でステートメント自体がただちに突然完了し、ステートメントの実行の通常モードでの後続のステップはすべて実行されません。
特に指定しないかぎり、評価されるすべての式とそれが実行されるすべてのサブステートメントが正常に完了すると、ステートメントは通常どおりに完了します。
ブロックは、一連の文、ローカル変数宣言文、および中カッコ内のローカル・クラス宣言とインタフェース宣言です。
{ [BlockStatements] }
各ローカル変数宣言文および他の文を、最初の文から最後の(左から右)まで順に実行して、ブロックを実行します。 これらのブロック文がすべて正常に完了すると、ブロックは正常に完了します。 これらのブロック文のいずれかがなんらかの理由で突然完了した場合、そのブロックは同じ理由で突然完了します。
ローカル・クラスはネストされたクラス(§8 (Classes))で、宣言はブロック(§14.2)によって即時に含まれます。
ローカル・インタフェースはネストされたインタフェース(§9 (インタフェース))で、宣言はブロックによって即時に含まれます。
便宜上、ここでは次のプロダクションを示します。
interface TypeIdentifier [TypeParameters] [InterfaceExtends] [InterfacePermits] InterfaceBody
ローカル・クラスおよびインタフェースの宣言は、含んでいるブロックの(ローカル変数宣言文を含む)文と自由に混ぜることができます。
ローカル・クラスまたはインタフェース宣言にアクセス修飾子public、protectedまたはprivate (§6.6)のいずれかがある場合、コンパイル時にエラーが発生します。
ローカル・クラスまたはインタフェース宣言に修飾子static (§8.1.1.4)、sealedまたはnon-sealed (§8.1.1.2、§9.1.1.4)が含まれている場合、コンパイル時にエラーが発生します。
ローカル・クラスの直接スーパークラスまたは直接スーパーインタフェースがsealedの場合、コンパイル時にエラーが発生します。
ローカル・インタフェースの直接スーパーインタフェースがsealedの場合、コンパイル時にエラーが発生します。
ローカル・クラスは、標準クラス(§8.1)、列挙クラス(§8.9)、またはレコード・クラス(§8.10)です。 各ローカル標準クラスは内部クラス(§8.1.3)です。 すべてのローカルenumクラスおよびローカル・レコード・クラスは暗黙的にstatic (§8.1.1.4)であるため、内部クラスではありません。
ローカル・インタフェースは、標準インタフェース(§9.1)にできますが、注釈インタフェース(§9.6)にはできません。 すべてのローカル・インタフェースは暗黙的にstaticです(§9.1.1.3)。
匿名クラス(§15.9.5)と同様に、ローカル・クラスまたはインタフェースは、パッケージ、クラスまたはインタフェースのメンバーではありません(§7.1、§8.5)。 匿名クラスとは異なり、ローカル・クラスまたはインタフェースには単純な名前(§6.2、§6.7)があります。
ローカル・クラスまたはインタフェース宣言のスコープおよびシャドウ化は、§6.3および§6.4で指定されています。
例14.3-1. ローカル・クラス宣言
前述のルールのいくつかの側面を示す例を次に示します:
class Global {
class Cyclic {}
void foo() {
new Cyclic(); // create a Global.Cyclic
class Cyclic extends Cyclic {} // circular definition
{
class Local {}
{
class Local {} // compile-time error
}
class Local {} // compile-time error
class AnotherLocal {
void bar() {
class Local {} // ok
}
}
}
class Local {} // ok, not in scope of prior Local
}
}
メソッドfooの最初の文は、ローカル・クラスCyclicのインスタンスではなく、メンバー・クラスGlobal.Cyclicのインスタンスを作成します。これは、この文がローカル・クラス宣言のスコープの前に出現するためです。
ローカル・クラス宣言のスコープがその宣言全体(本体のみでなく)を包含しているという事実は、ローカル・クラスCyclicの定義がGlobal.Cyclicではなく自身を拡張するため、実際には循環していることを意味します。 その結果、ローカル・クラスCyclicの宣言はコンパイル時に拒否されます。
ローカル・クラス名は同じメソッド(またはコンストラクタまたはイニシャライザ)内で再宣言できないため、Localの2番目と3番目の宣言ではコンパイル時にエラーが発生します。 ただし、Localは、AnotherLocalなどのより深くネストされた別のクラスのコンテキストで再宣言できます。
Localの最終宣言は、Localの以前の宣言の範囲外で発生するため、有効です。
ローカル変数宣言は、1つ以上のローカル変数を宣言し、オプションで初期化します(§4.12.3)。
var
UnannTypeについては、§8.3を参照してください。 ここでは、便宜上、§4.3、§8.3、および §8.4.1からの次のプロダクションを示します。
final
_
ローカル変数宣言は次の場所で使用できます。
局所変数宣言の注釈修飾子に関する規則は、§9.7.4および§9.7.5で指定されています。
キーワードfinalがローカル変数宣言の修飾子として表示される場合、ローカル変数はfinal変数です(§4.12.4)。
finalがローカル変数宣言の修飾子として複数回出現した場合、コンパイル時にエラーが発生します。
(i)に識別子が含まれず、(ii)イニシャライザを持たないローカル変数宣言が、次のいずれかの場所に表示される場合、コンパイル時にエラーが発生します。
LocalVariableTypeがvarで、次のいずれかがtrueの場合、コンパイル時にエラーが発生します。
複数のVariableDeclaratorがリストされています。
VariableDeclaratorIdには、1つ以上のカッコのペアがあります。
VariableDeclaratorにはイニシャライザがありません。
VariableDeclaratorのイニシャライザはArrayInitializerです。
VariableDeclaratorのイニシャライザには、変数の参照が含まれます。
例14.4-1. varで宣言されたローカル変数
次のコードは、varの使用を制限するこれらのルールを示しています。
var a = 1; // Legal
var b = 2, c = 3.0; // Illegal: multiple declarators
var d[] = new int[4]; // Illegal: extra bracket pairs
var e; // Illegal: no initializer
var f = { 6 }; // Illegal: array initializer
var g = (g = 7); // Illegal: self reference in initializer
これらの制限は、varで表される型に関する混乱を回避するために役立ちます。
ローカル変数宣言内の各宣言子は、1つのローカル変数を宣言します。 宣言子に識別子が含まれている場合、これはローカル変数の名前です。それ以外の場合、ローカル変数は名前なしです(§6.1)。
オプションのキーワードfinalが宣言の開始時に出現する場合、宣言される変数は最終変数です(§4.12.4)。
ローカル変数の宣言型は、次のように決定されます:
LocalVariableTypeがUnannTypeで、UnannTypeまたはVariableDeclaratorIdにカッコのペアが表示されない場合、ローカル変数のタイプはUnannTypeで示されます。
LocalVariableTypeがUnannTypeで、カッコのペアがUnannTypeまたはVariableDeclaratorIdに指定されている場合、ローカル変数のタイプは§10.2で指定されます。
LocalVariableTypeがvarの場合、Tは、代入コンテキストに存在しないかのように扱われ、スタンドアロン式(§15.2)として扱われるときに、イニシャライザ式のタイプになります。 ローカル変数の型は、T (§4.10.5)で言及されているすべての合成型変数に関する Tの上方投影です。
Tがnull型の場合はコンパイル時にエラーが発生します。
イニシャライザは代入コンテキストに存在しなかったかのように扱われるため、ラムダ式(§15.27)またはメソッド参照式(§15.13)の場合、エラーが発生します。
局所変数宣言のスコープとシャドウ化は、§6.3および §6.4で規定されています。
ネストされたクラスまたはインタフェース、またはラムダ式からのローカル変数への参照は、§6.5.6.1で指定されているように制限されます。
例14.4.1-1 varで宣言されたローカル変数のタイプ
次のコードは、varで宣言された変数の型付けを示しています。
var a = 1; // a has type 'int'
var b = java.util.List.of(1, 2); // b has type 'List<Integer>'
var c = "x".getClass(); // c has type 'Class<? extends String>'
// (see JLS 15.12.2.6)
var d = new Object() {}; // d has the type of the anonymous class
var e = (CharSequence & Comparable<String>) "x";
// e has type CharSequence & Comparable<String>
var f = () -> "hello"; // Illegal: lambda not in an assignment context
var g = null; // Illegal: null type
varで宣言された一部の変数は、明示的な型では宣言できません。これは、変数の型が否定可能ではないためです。
変数の型を決定するときに、上向き投影がイニシャライザの型に適用されます。 イニシャライザのタイプにキャプチャ変数が含まれている場合、このプロジェクションはイニシャライザのタイプをキャプチャ変数を含まないスーパータイプにマップします。
変数の型が取得変数を指定できるようにすることもできますが、それらを別の場所に投影することで、取得変数の有効範囲が、型が取得される式を含む文より大きくなることはないという魅力的な不変性が強制されます。 非公式には、取得変数は後続の文に"leak"できません。
ローカル変数宣言文は、ローカル変数宣言で構成されます。
すべての局所変数宣言文はブロックによってすぐに含まれますが、他の種類の文(§14.5)はブロックまたは別の文によってすぐに含まれます。
包含ブロックでは、ローカル変数宣言文は、他の種類の文と、ローカル・クラスおよびインタフェース宣言と自由に混在できます。
ローカル変数宣言文は実行可能文です。 実行されるたびに、宣言子は左から右の順に処理されます。 宣言子にイニシャライザがある場合、イニシャライザが評価され、その値が変数に割り当てられます。
宣言子にイニシャライザがない場合、変数へのすべての参照の前に変数への代入を実行する必要があり、そうでない場合は、§16 (Definite Assignment)のルールでコンパイル時にエラーが発生します。
各イニシャライザ(最初のイニシャライザを除く)は、前のイニシャライザの評価が正常に完了した場合にのみ評価されます。
ローカル変数宣言文の実行は通常、最後のイニシャライザの評価が正常に完了した場合にのみ完了します。
ローカル変数宣言文の宣言子にイニシャライザが含まれていない場合、文の実行は常に正常に完了します。
Javaプログラミング言語には多くの種類の文があります。 ほとんどの文は、CおよびC++言語の文に対応していますが、一部は一意です。
CおよびC++と同様に、Javaプログラミング言語のif文は、この誤解を招く形式の例で示した、いわゆる「ダングリングelse問題」に悩まされています。
if (door.isOpen())
if (resident.isVisible())
resident.greet("Hello!");
else door.bell.ring(); // A "dangling else"
問題は、外部のif文と内部if文の両方が、おそらくelse句を所有していることです。 この例では、プログラマがelse句を外部のif文に所属させることを想定していることがわかります。
CやC++などのJavaプログラミング言語とその前の多くのプログラミング言語は、else句が属している可能性のある最も内側のifに属することを任意に決定します。 このルールは、次の文法で取得されます。
ここでは、便宜上、§14.9からの次のプロダクションを示します。
if ( 式 ) StatementNoShortIf else 文
if ( 式 ) StatementNoShortIf else StatementNoShortIf
したがって、文は文法的に2つのカテゴリに分けられます。つまり、else句がないif文(短いif文)で終わる場合があり、そうでない場合です。
短いif文で完全に終わらない文のみが、else句を持つif文でキーワードelseの前に即時のサブステートメントとして出現する可能性があります。
この単純なルールにより、ダングリングelseの問題が回避されます。 短いif制限のない文の実行動作は、「短いifなし」制限のない同じ種類の文の実行動作と同じです。この区別は、構文上の難しさを解決するためにのみ描画されます。
空の文は何も行いません。
;
空の文の実行は、常に正常に完了します。
文にはラベル接頭辞を含めることができます。
識別子は、すぐに含まれる文のラベルとして宣言されます。
CおよびC++とは異なり、Javaプログラミング言語にはgoto文はありません。識別子文のラベルは、ラベル付き文内の任意の場所に表示されるbreak文またはcontinue文(§14.15、§14.16)とともに使用されます。
ラベル付き文のラベルの有効範囲は、すぐに含まれる文です。
ラベル付きステートメントの名前がラベルのスコープ内で別のラベル付きステートメントのラベルとして使用される場合、コンパイル時にエラーが発生します。
ラベルと同じ識別子を使用し、パッケージ、クラス、インタフェース、メソッド、フィールド、パラメータまたはローカル変数の名前として使用することに対する制限はありません。 文にラベルを付けるための識別子の使用は、同じ名前のパッケージ、クラス、インタフェース、メソッド、フィールド、パラメータまたはローカル変数を不明瞭化しません(§6.4.2)。 クラス、インタフェース、メソッド、フィールド、ローカル変数、または例外ハンドラ(§14.20)のパラメータとしての識別子の使用は、同じ名前の文ラベルを隠すものではありません。
ラベル付き文は、すぐに含まれる文を実行することによって実行されます。
文に識別子のラベルが付けられ、含まれている文が、同じ識別子を持つbreakのために突然完了した場合、ラベル付き文は正常に完了します。 その他すべての文が突然完了した場合、ラベル付き文は同じ理由で突然完了します。
例14.7-1. ラベルと識別子
次のコードは、最初にラベルがtestと呼ばれていたクラスStringとそのメソッドindexOfのバージョンから取得されました。 ローカル変数iと同じ名前になるようにラベルを変更しても、i宣言のスコープ内のラベルは不明瞭化されません。 したがって、コードは有効です。
class Test {
char[] value;
int offset, count;
int indexOf(TestString str, int fromIndex) {
char[] v1 = value, v2 = str.value;
int max = offset + (count - str.count);
int start = offset + ((fromIndex < 0) ? 0 : fromIndex);
i:
for (int i = start; i <= max; i++) {
int n = str.count, j = i, k = str.offset;
while (n-- != 0) {
if (v1[j++] != v2[k++])
continue i;
}
return i - offset;
}
return -1;
}
}
識別子maxも文ラベルとして使用でき、ラベルはラベル付き文内のローカル変数maxを不明瞭化しません。
特定の種類の式は、セミコロンを付けて文として使用できます。
式文は、式を評価することによって実行されます。式に値がある場合、その値は破棄されます。
式の実行は、式の評価が正常に完了した場合にのみ、正常に完了します。
CやC++とは異なり、Javaプログラミング言語では、特定の形式の式のみを式文として使用できます。 たとえば、メソッド呼出し式(§15.12)を使用することは有効です。
System.out.println("Hello world"); // OK
ただし、カッコで囲まれた式(§15.8.5)を使用することは無効です。
(System.out.println("Hello world")); // illegal
Javaプログラミング言語では、voidへのキャストは許可されません。つまり、voidは型ではないため、従来のCでは次のような式文を記述することができません。
(void)... ; // incorrect!
動作しません。 一方、Javaプログラミング言語では、式文の中で最も有用な種類の式をすべて使用でき、voidメソッドを呼び出すための式文として使用されるメソッド呼出しを必要としないため、このようなトリックはほとんど必要ありません。 トリックが必要な場合は、代入文(§15.26)または局所変数宣言文(§14.4)のいずれかをかわりに使用できます。
if文
if文では、文または2つの文の条件付き実行を許可し、どちらか一方を実行しますが、両方は実行できません。
if ( 式 ) StatementNoShortIf else 文
if ( 式 ) StatementNoShortIf else StatementNoShortIf
「式」のタイプはbooleanまたはBooleanである必要があります。そうしないと、コンパイル時にエラーが発生します。
if-then文
if-then文は、最初に式を評価することによって実行されます。 結果のタイプがBooleanの場合、ボックス化解除変換の対象となります(§5.1.8)。
式または後続のボックス化解除変換(ある場合)の評価がなんらかの理由で突然完了した場合、if-then文も同じ理由で突然完了します。
それ以外の場合は、結果の値に基づいて実行が続行されます。
値がtrueの場合は、含まれている文が実行され、if-then文は、文の実行が正常に完了した場合にのみ正常に完了します。
値がfalseの場合、これ以上のアクションは実行されず、if-then文が正常に完了します。
if-then-else文
if-then-else文は、最初に式を評価することによって実行されます。 結果のタイプがBooleanの場合、ボックス化解除変換の対象となります(§5.1.8)。
式または後続のボックス化解除変換(ある場合)の評価がなんらかの理由で突然完了した場合、if-then-else文も同じ理由で突然完了します。
それ以外の場合は、結果の値に基づいて実行が続行されます。
値がtrueの場合、最初に含まれる文(elseキーワードの前の文)が実行されます。if-then-else文は、その文の実行が正常に完了した場合にのみ、正常に完了します。
値がfalseの場合、2番目の文(elseキーワードの後に続く文)が実行されます。if-then-else文は、その文の実行が正常に完了した場合にのみ、正常に完了します。
assert文
アサーションは、ブール式を含むassert文です。 アサーションは、有効または無効のいずれかです。 アサーションが有効な場合、アサーションを実行するとブール式が評価され、式がfalseと評価されるとエラーが報告されます。 アサーションが無効になっている場合、アサーションの実行はまったく効果がありません。
プレゼンテーションを容易にするために、両方の形式のassert文の最初の式を式1と呼びます。 assert文の2番目の形式では、2番目の式を式2と呼びます。
Expression1のタイプがbooleanまたはBooleanでない場合、コンパイル時にエラーが発生します。
assert文の2番目の形式でExpression2が無効(§15.1)の場合、コンパイル時にエラーが発生します。
クラスまたはインタフェースが初期化を完了した後に実行されるassert文は、ホスト・システムがassert文を含む最上位クラスまたはインタフェースがアサーションを有効にしていると判断した場合にのみ有効になります。
最上位のクラスまたはインタフェースがアサーションを有効にするかどうかは、(i)最上位のクラスまたはインタフェースの初期化、および(ii)最上位のクラスまたはインタフェースにネストされたクラスまたはインタフェースの初期化のうち、最も早いものまで決定されます。 トップ・レベル・クラスまたはインタフェースでアサーションを有効にするかどうかは、確認後に変更できません。
クラスまたはインタフェースの初期化が完了する前に実行されるassert文が有効になります。
このルールは、特別な治療を必要とするケースによって動機付けられます。 クラスのアサーション・ステータスは、初期化される時間より後に設定されることを思い出してください。 一般的に望ましくはありませんが、メソッドやコンストラクタは、初期化の前に実行できます。 これは、次の例のように、クラス階層の静的初期化に循環が含まれている場合に発生することがあります。
public class Foo {
public static void main(String[] args) {
Baz.testAsserts();
// Will execute after Baz is initialized.
}
}
class Bar {
static {
Baz.testAsserts();
// Will execute before Baz is initialized!
}
}
class Baz extends Bar {
static void testAsserts() {
boolean enabled = false;
assert enabled = true;
System.out.println("Asserts " +
(enabled ? "enabled" : "disabled"));
}
}
Baz.testAsserts()を起動すると、Bazが初期化されます。 これが発生する前に、Barを初期化する必要があります。 Barの静的イニシャライザは、Baz.testAsserts()を再度呼び出します。 Bazの初期化は現在のスレッドによってすでに進行中であるため、Bazは初期化されませんが(§12.4.2)、2番目の起動はただちに実行されます。
前述のルールのため、前述のプログラムがアサーションを有効にせずに実行された場合、次の内容を出力する必要があります。
Asserts enabled Asserts disabled
無効化されたassert文は何も行いません。 特に、Expression1もExpression2も(存在する場合)評価されません。 無効化されたassert文の実行は、常に正常に完了します。
有効なassert文は、最初にExpression1を評価することによって実行されます。 結果のタイプがBooleanの場合、ボックス化解除変換の対象となります(§5.1.8)。
Expression1の評価または後続のアンボクシング変換(ある場合)がなんらかの理由で突然完了した場合、assert文も同じ理由で突然完了します。
それ以外の場合、実行はExpression1の値に基づいて選択することで続行されます。
値がtrueの場合、これ以上のアクションは実行されず、assert文は正常に完了します。
値がfalseの場合、実行動作はExpression2が存在するかどうかによって異なります。
Expression2が存在する場合は、評価されます。 次に、
何らかの理由で評価が突然完了した場合、assert文も同じ理由で突然完了します。
評価が正常に完了すると、「詳細メッセージ」がExpression2の結果の値であるAssertionErrorインスタンスが作成されます。 次に、
インスタンスの作成がなんらかの理由で突然完了した場合、assert文も同じ理由で突然完了します。
インスタンスの作成が正常に完了すると、新しく作成されたAssertionErrorオブジェクトをスローして、assert文が突然完了します。
Expression2が存在しない場合は、ディテール・メッセージのないAssertionErrorインスタンスが作成されます。 次に、
インスタンスの作成がなんらかの理由で突然完了した場合、assert文も同じ理由で突然完了します。
インスタンスの作成が正常に完了すると、新しく作成されたAssertionErrorオブジェクトをスローして、assert文が突然完了します。
通常、アサーション・チェックは、プログラムの開発およびテスト中に有効化され、デプロイメントのために無効化されてパフォーマンスが向上します。
アサーションを無効にできるので、プログラムでは、アサーションに含まれる式が評価されることを想定してはなりません。 したがって、これらのブール式は一般に副作用がないはずです。 このようなブール式を評価しても、評価の完了後に表示される状態には影響しません。 アサーションに含まれるブール式が副作用を持つことは違法ではありませんが、アサーションが有効か無効かによってプログラムの動作が異なる可能性があるため、通常は不適切です。
これを考慮して、publicメソッドの引数チェックにアサーションを使用しないでください。 引数チェックは通常、メソッドの規約の一部であり、アサーションが有効か無効かに関係なく、この規約を支持する必要があります。
引数チェックにアサーションを使用する場合の2つ目の問題は、誤った引数によって適切な実行時例外(IllegalArgumentException、ArrayIndexOutOfBoundsException、NullPointerExceptionなど)が発生することです。 アサーションが失敗しても、適切な例外はスローされません。 繰り返しますが、publicメソッドの引数チェックにアサーションを使用することは違法ではありませんが、通常は不適切です。 AssertionErrorは捕捉されないことを意図していますが、捕捉することは可能であるため、try文のルールは、throw文の現在の処理と同様に、tryブロックに表示されるアサーションを処理する必要があります。
switch文
switch文は、式の値に応じて、制御を複数の文または式のいずれかに転送します。
switch ( 式 ) SwitchBlock
式は、セレクタ式と呼ばれます。 セレクタ式の型は、char、byte、short、intまたは参照型である必要があります。そうしないと、コンパイル時にエラーが発生します。
switch文とswitch式(§15.28)の両方の本体は、スイッチ・ブロックと呼ばれます。 このサブセクションは、switch文またはswitch式のいずれに表示されるかに関係なく、すべてのスイッチ・ブロックに適用される一般的なルールを示します。 その他のサブセクションには、switch文のswitchブロック(§14.11.2)またはswitch式のswitchブロック(§15.28.1)に適用される追加ルールがあります。
{ SwitchRule {SwitchRule} } { {SwitchBlockStatementGroup} {SwitchLabel :} }
-> 式 ; -> ブロック -> ThrowStatement
case CaseConstant {, CaseConstant} case null [, default] case CasePattern {, CasePattern} [Guard] default
when 式
switchブロックは次のいずれかで構成できます。
スイッチ・ルール: ->を使用して、スイッチ・ルール式、スイッチ・ルール・ブロックまたはスイッチ・ルールthrow文を導入します。
ラベル付き文グループの切替え: :を使用してラベル付きブロック文の切替えを導入します。
すべてのswitchルールおよびswitchラベル付き文グループは、caseラベルまたはdefaultラベルのいずれかであるswitch labelで始まります。 複数のswitchラベルが、1つのswitchラベルが付いた文グループに許可されます。
caseラベルには、case定数の(空でない)リスト、nullリテラルまたはcaseパターンの(空でない)リストがあります。
すべてのcase定数は、定数式(§15.29)または列挙定数の名前(§8.9.1)のいずれかにする必要があります。そうしないと、コンパイル時にエラーが発生します。
nullリテラルを含むcaseラベルには、オプションのdefaultを指定できます。
caseパターンを含むcaseラベルには、オプションのwhen式(ガードと呼ばれる)があり、これはパターンに一致する値に対するさらなるテストを表します。 caseラベルは、(i)ガードがない場合、または(ii)値trueを持つ定数式(§15.29)であるガードがある場合、ガードなしであるとみなされます。それ以外の場合は、ガード付きです。
caseラベルが複数のcaseパターンを持ち、パターン変数(caseラベルに関連付けられたガードによって宣言されたものを除く)を宣言する場合、コンパイル時にエラーが発生します。
複数のcaseパターンを持つcaseラベルがパターン変数を宣言できる場合、caseラベルを適用する場合に初期化される変数は明確ではありません。 たとえば:
Object obj = ...;
switch (obj) {
case Integer i, Boolean b -> {
... // Error! Is i or b initialized?
}
...
}
caseパターンの1つのみがパターン変数を宣言している場合でも、変数が初期化されたかどうかは不明です。たとえば:
Object obj = ...;
switch (obj) {
case Integer i, Boolean _ -> {
... // Error! Is i initialized?
}
...
}
次の場合はコンパイル時にエラーが発生しません:
Object obj = ...;
switch (obj) {
case Integer _, Boolean _ -> {
... // Matches both an Integer and a Boolean
}
...
}
スイッチ・ラベルとそのcase定数、nullリテラルおよびcaseパターンは、スイッチ・ブロックに関連付けられると言われています。
特定のswitchブロックの場合、次の両方が当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します:
スイッチ・ブロックに関連付けられているcase定数のうち、同じ値を持つものはどれもありません。
スイッチ・ブロックに関連付けることができるnullリテラルは1つのみです。
1つのスイッチ・ブロックに関連付けることができるdefaultラベルは1つのみです。
caseラベルに関連付けられたガードは、次の条件をすべて満たす必要があります。そうしないと、コンパイル時にエラーが発生します。
ガードの型はbooleanまたはBooleanである必要があります。
ガードで使用されるが宣言されていないローカル変数、仮パラメータまたは例外パラメータは、finalまたは事実上final (§4.12.4)である必要があります。
ガードで使用されるが宣言されていない空白のfinal変数は、ガードの前に必ず割り当てる必要があります(§16 (Definite Assignment))。
ガードは、値falseを持つ定数式(§15.29)にはできません。
次のすべてに当てはまる場合、switch文またはswitch式のswitchブロックは、セレクタ式の型Tとのswitch compatibleです。
スイッチ・ブロックは、boolean、long、floatおよびdouble型と連動するように設計されていません。 switch文またはswitch式のセレクタ式には、これらのタイプのいずれも指定できません。
switch文またはswitch式のswitchブロックは、セレクタ式の型とのswitch互換性がある必要があるか、コンパイル時にエラーが発生します。
switchブロック内のswitchラベルは、それが適用されるすべての値に対して、前述のswitchラベルの1つも適用されると判断できる場合に、優位であるとみなされます。 switchブロック内のどのswitchラベルも優先されない場合、コンパイル時にエラーになります。 switchラベルが優先されるかどうかを判断するするためのルールは次のとおりです。
caseパターンqを持つcaseラベルは、caseパターンpを持つswitchブロックに先行するガードなしcaseラベルがあり、pがq(§14.30.3)より優れている場合に優先されます。
パターンが別のパターンより優先される定義は型に基づきます。 たとえば、型パターンObject oは型パターンString sより優位であるため、コンパイル時にエラーが発生します。
Object obj = ...
switch (obj) {
case Object o ->
System.out.println("An object");
case String s -> // Error!
System.out.println("A string");
}
caseパターンを持つガード付きcaseラベルは、同じパターンを持つがガードを持たないcaseラベルによって支配されます。 たとえば、次はコンパイル時にエラーになります。
String str = ...;
switch (str) {
case String s ->
System.out.println("A string");
case String s when s.length() == 2 -> // Error!
System.out.println("Two character string");
...
}
一方、caseパターンを持つガード付きcaseラベルは、同じcaseパターンを持つガードなしcaseラベルより優位であるとはみなされません。 これにより、次の一般的なパターン・プログラミング・スタイルを使用できます:
Integer j = ...;
switch (j) {
case Integer i when i <= 0 ->
System.out.println("Less than or equal to zero");
case Integer i ->
System.out.println("An integer");
}
唯一の例外は、ガードが値trueを持つ定数式である場合です。次に例を示します。
Integer j = ...;
switch (j) {
case Integer i when true -> // Ok
System.out.println("An integer");
case Integer i -> // Error!
System.out.println("An integer");
}
複数のcaseパターンを持つcaseラベルは、これらのパターンのいずれかが、先行するガードなしのcaseラベルでcaseパターンとして表示されるパターンによって支配されている場合に支配されるため、コンパイル時にエラーが発生します(型パターンInteger _は型パターンNumber _によって支配されるため)。
Object obj = ...
switch (obj) {
case Number _ ->
System.out.println("A Number");
case Integer _, String _ -> // Error - dominated!
System.out.println("An Integer or a String");
...
}
case定数cを持つcaseラベルは、次のいずれかが保持される場合に優先されます。
cはプリミティブ型Sの定数式であり、スイッチ・ブロックには、ガードなしのcaseパターンpを持つ先行するcaseラベルがあります。ここで、pはSのラッパー・クラスに対して無条件です。
cは、参照型Tの定数式であり、スイッチ・ブロックには、ガードなしのcaseパターンpを持つ先行するcaseラベルがあります。ここで、pは型Tに対して無条件です。
cはenumクラスEの列挙定数を指定し、スイッチ・ブロックには、ガードなしのcaseパターンpを持つ先行するcaseラベルがあります。この場合、pは型Eに対して無条件です。
たとえば、Integer型パターンを持つcaseラベルは、整数リテラルを持つcaseラベルより優位です。
Integer j = ...;
switch (j) {
case Integer i ->
System.out.println("An integer");
case 42 -> // Error - dominated!
System.out.println("42!");
}
defaultラベルまたはcase null, defaultラベルは、caseパターンpを持つswitchブロックに先行するガードなしcaseラベルがある場合、支配されます。ここで、pはセレクタ式の型(§14.30.3)に対して無条件です。
セレクタ式の型に対して無条件であるcaseパターンを持つcaseラベルは、名前が示すとおり、すべての値に一致するため、defaultラベルのように動作します。 スイッチ・ブロックは、defaultのように動作する複数のスイッチ・ラベルを持つことはできません。
パターンpi (1≤i<n)のいずれかがパターンpj (i<j≤n)の別のパターン(i<j≤n)のcaseラベルが存在する場合、コンパイル時にエラーが発生します。
次のいずれかに該当する場合は、コンパイル時にエラーが発生します:
switchブロックには、caseパターンを持つcaseラベルの前にdefaultラベルがあります。
switchブロックには、caseラベルの前にnullリテラルが付いたdefaultラベルがあります。
switchブロックにはcase null, defaultラベルがあり、その後に他のswitchラベルが続きます。
使用する場合、defaultラベルはスイッチ・ブロックの最後に配置する必要があります。
互換性のため、nullリテラルまたはcaseパターンを持たないcaseラベルの前にdefaultラベルが表示される場合があります。
int i = ...;
switch(i) {
default ->
System.out.println("Some other integer");
case 42 -> // allowed
System.out.println("42");
}
使用する場合は、case null, defaultラベルをスイッチ・ブロックの最後に配置する必要があります。
switchラベル付き文グループで構成されるswitchブロックで、文に1つ以上のパターン変数(§6.3.3)を宣言するcaseラベルが付けられ、次のいずれかの場合、コンパイル時にエラーが発生します。
switchブロックの直前の文が正常に完了できる(§14.22)、または
文に複数のswitchラベルが付けられている。
最初の条件により、文グループがパターン変数を初期化せずに別の文グループにフォール・スルーすることがなくなります。 たとえば、前述の文グループから到達可能なcase Integer iというラベルが付いた文の場合、パターン変数iは初期化されていません。
Object o = "Hello";
switch (o) {
case String s:
System.out.println("String: " + s ); // No break!
case Integer i:
System.out.println(i + 1); // Error! Can be reached
// without matching the
// pattern `Integer i`
default:
}
switchラベル文グループで構成されるswitchブロックでは、複数のラベルを文グループに適用できます。 2番目の条件は、別のラベルのパターン変数を初期化せずに、あるラベルに基づいて文グループが実行されないようにします。 たとえば:
Object o = "Hello World";
switch (o) {
case String s:
case Integer i:
System.out.println(i + 1); // Error! Can be reached
// without matching the
// pattern `Integer i`
default:
}
Object obj = null;
switch (obj) {
case null:
case String s:
System.out.println(s); // Error! Can be reached
// without matching the
// pattern `String s`
default:
}
これらの条件はどちらも、caseパターンがパターン変数を宣言する場合にのみ適用されます。 一方、次の例は問題ありません:
record R() {}
record S() {}
Object o = "Hello World";
switch (o) {
case String s:
System.out.println(s); // No break
case R(): // No pattern variables declared
System.out.println("It's either an R or a string");
break;
default:
}
Object ob = new R();
switch (ob) {
case R():
case S(): // Multiple case labels
System.out.println("Either R or an S");
break;
default:
}
Object obj = null;
switch (obj) {
case null:
case R(): // Multiple case labels
System.out.println("Either null or an R");
break;
default:
}
switch式またはswitch文のswitchブロックは、次のいずれかの場合にセレクタ式eに対して完全です。
switchブロックに関連付けられたdefaultラベルがあります。
スイッチ・ブロックに関連付けられたcase null, defaultラベルがあります。
スイッチ・ブロックに関連付けられたガードなしのcaseラベル(総称してcase要素)に表示されるすべてのcase定数およびcaseパターンを含むセットは空ではなく、セレクタ式eのタイプをカバーします。
case要素のセット(P)は、次のいずれかの場合に型Tを検出します。
Pはタイプ Uをカバーし、Tと Uは同じ消去を持ちます。
Pには、T (§14.30.3)に対して無条件のパターンが含まれています。
Tは、上限 Bを持つ型変数であり、Pは Bをカバーします。
Tは交差型 T1& ... &Tnで、Pは Tiをカバーし、Ti (1≤ i ≤ n)のいずれかのタイプを対象とします。
型 Tは列挙クラス型 Eで、Pは Eの列挙定数のすべての名前を含みます。
defaultラベルは、すべての列挙定数の名前がcase定数として表示される場合に許可されますが、必須ではありません。 たとえば:
enum E { F, G, H }
static int testEnumExhaustive(E e) {
return switch(e) {
case F -> 0;
case G -> 1;
case H -> 2; // No default required!
};
}
タイプTは、abstract sealedクラスまたはsealedインタフェースにCという名前を付け、許可されるCのダイレクト・サブクラスまたはサブインタフェースDごとに、次の2つの条件のいずれかを保持します。
Dという名前で、Tのサブタイプである型は存在しない。または
Dという名前で Tのサブタイプであるタイプ Uがあり、Pは Uをカバーします。
スイッチ・ブロックがabstract sealedクラスまたはsealedインタフェースの許可されているすべての直接サブクラスおよびサブインタフェースを使い果たす場合は、defaultラベルを使用できますが、必須ではありません。 たとえば:
sealed interface I permits A, B, C {}
final class A implements I {}
final class B implements I {}
record C(int j) implements I {} // Implicitly final
static int testExhaustive1(I i) {
return switch(i) {
case A a -> 0;
case B b -> 1;
case C c -> 2; // No default required!
};
}
switchブロックには、A型、B型およびC型のすべての値と一致するcaseパターンが含まれ、I型の他のインスタンスは許可されないため、このswitchブロックは完全です。
許可された直接サブクラスまたはサブインタフェースは、汎用のsealedスーパークラスまたはスーパーインタフェースの特定のパラメータ化のみを拡張できるという事実は、スイッチ・ブロックが完全であるかどうかを判断する際に必ずしも考慮する必要がないことを意味します。 たとえば:
sealed interface J<X> permits D, E {}
final class D<Y> implements J<String> {}
final class E<X> implements J<X> {}
static int testExhaustive2(J<Integer> ji) {
return switch(ji) { // Exhaustive!
case E<Integer> e -> 42;
};
}
セレクタ式のタイプがJ<Integer>であるため、jiの値をDのインスタンスにすることはできないため、許可される直接サブクラスDは考慮する必要はありません。
タイプTはレコード・クラスRに名前を付け、Pには、Rという名前のタイプを持つレコード・パターンpと、タイプUのRのすべてのレコード・コンポーネント(存在する場合)は、対応するコンポーネント・パターンpを含むシングルトン・セットがUをカバーします。
対応するレコード・コンポーネントの型をすべてカバーするコンポーネント・パターンは、レコード型をカバーするとみなされます。 たとえば:
record Test<X>(Object o, X x){}
static int testExhaustiveRecordPattern(Test<String> r) {
return switch(r) { // Exhaustive!
case Test<String>(Object o, String s) -> 0;
};
}
Pはセット Qに書き換え、Qは Tをカバーします。
Pのサブセットがパターンpに縮小され、QがパターンpとともにPの残りの要素で構成されている場合、P、rewrites、rewrites、QのセットがセットQに設定されます。
次のいずれかが保持されている場合、空でないパターンのセット RPは、reducesを単一のパターン RPに削減します。
RPはある種の Uをカバーし、RPは U型のパターンです。
RP consists of record patterns whose types all erase to the same record class R with k (k≥1) components and there is a distinguished component cr (1≤r≤k) of R such that for every other component ci (1≤i≤k, i≠r) the set containing the component patterns from the record patterns corresponding to component ci is equivalent to a single pattern qi, the set containing the component patterns from the record patterns corresponding to the component cr reduces to a single pattern q, and rp is the record pattern of type R with a pattern list consisting of the patterns q1, ..., qr-1, q, qr+1, ..., qk.
パターン EPの空でないセットは、次のいずれかが保持されている場合、単一のパターン EPと同等です。
EPは、すべての型が同じ消去 Tを持つ型パターンで構成され、EPは T型の型パターンです。
EPは、k (k≥1)コンポーネントを持つ同じレコード・クラスRにすべての型が消去されるレコード・パターンと、レコード・コンポーネントごとに、レコードから対応するコンポーネント・パターンを含むセットで構成されます。パターンは、単一のパターンqj (1≤j≤k)と同等であり、EPは、コンポーネント・パターンq1、...qkで構成されるコンポーネント・パターン・リストを持つR型のレコード・パターンです。
通常、レコード・パターンはレコード型の値のサブセットにのみ一致します。 ただし、switchブロック内の複数のレコード・パターンを組み合せて、実際にレコード型のすべての値に一致させることができます。 たとえば:
sealed interface I permits A, B, C {}
final class A implements I {}
final class B implements I {}
record C(int j) implements I {} // Implicitly final
record Box(I i) {}
int testExhaustiveRecordPatterns(Box b) {
return switch (b) { // Exhaustive!
case Box(A a) -> 0;
case Box(B b) -> 1;
case Box(C c) -> 2;
};
}
このswitchブロックが網羅的かどうかを判断するには、レコード・パターンの組合せを分析する必要があります。 レコード・パターンBox(I i)を含むセットはBox型をカバーするため、パターンBox(A a)、Box(B b)およびBox(C c)を含むセットは、パターンBox(I i)を含むセットにリライトできます。 これは、パターンA a、B b、C cを含むセットがパターンI iに減り(同じセットがタイプIをカバーするため)、パターンBox(A a)、Box(B b)、Box(C c)を含むセットがパターンBox(I i)に減るためです。
ただし、レコード・パターンのセットの書き換えは必ずしも単純ではありません。 たとえば:
record IPair(I i, I j){}
int testNonExhaustiveRecordPatterns(IPair p) {
return switch (p) { // Not Exhaustive!
case IPair(A a, A a) -> 0;
case IPair(B b, B b) -> 1;
case IPair(C c, C c) -> 2;
};
}
前述の例のロジックを適用して、パターンIPair(A a, A a)、IPair(B b, B b)、IPair(C c, C c)を含むセットをパターンIPair(I i, I j)を含むセットにリライトし、スイッチ・ブロックによってタイプIPairが枯渇すると結論付けています。 ただし、これは正しくありません。たとえば、switchブロックには、最初のコンポーネントがA値で、2番目のコンポーネントがB値であるIPair値に一致するラベルが実際にはありません。 あるコンポーネントのレコード・パターンを組み合せることは、その他のコンポーネントの同じ値と一致する場合にのみ有効です。 たとえば、3つのレコード・パターンIPair(A a, I i)、IPair(B b, I i)およびIPair(C c, I i)を含むセットは、パターンIPair(I j, I i)に減らすことができます。
switch文または式は、そのswitchブロックがセレクタ式に対して完全である場合、完全です。
switch文の実行(§14.11.3)とswitch式の評価(§15.28.2)の両方で、switchブロックに関連付けられたswitchラベルがセレクタ式の値に適用されるかどうかを判断する必要があります。 これは次のように処理されます。
値がNULL参照の場合は、nullリテラルを含むcaseラベルが適用されます。
値がnull参照ではない場合、次のように、値に適用されるswitchブロック内の最初の(ある場合)caseラベルが決定されます。
case定数cを持つcaseラベルは、値が最初にボックス化解除変換(§5.1.8)の対象で、定数cがボックス化解除値と等しい場合に、Character、Byte、ShortまたはInteger型の値に適用されます。
ボックス化解除変換は、ボックス化解除される値がNULL参照にならないことが保証されるため、正常に完了します。
等価は、==演算子(§15.21)によって定義されます。
case定数cを持つcaseラベルは、char型、byte型、short型、int型またはString型(定数cが値と等しい場合)に適用されます。
等価は、値がStringでないかぎり==演算子によって定義されます。この場合、等価はクラスStringのequalsメソッドによって定義されます。
caseパターンがp1、...、pn (n≥1)のcaseラベルが値に適用されることを確認するには、その値に適用される最初の(ある場合) caseパターンpi (1≤i≤n)を検索します。
caseパターンが値に適用されることを確認するには、まず値がパターンと一致することをチェックします(§14.30.2)。 次に、
パターン・マッチングが突然完了した場合は、適用されるswitchラベルを判断するプロセス全体が同じ理由で突然完了します。
パターン一致が成功し、caseラベルが保護されていない場合、このcaseパターンが適用されます。
パターン一致が成功し、caseラベルがガードされている場合、ガードが評価されます。 結果のタイプがBooleanの場合、ボックス化解除変換の対象となります(§5.1.8)。
ガードまたは後続のボックス化解除変換(ある場合)の評価がなんらかの理由で突然完了した場合、適用するswitchラベル・パターンを判断するプロセス全体が同じ理由で突然完了します。
それ以外の場合は、結果の値がtrueの場合、caseパターンが適用されます。
case null, defaultラベルは、すべての値に適用されます。
値がNULL参照ではなく、ステップ2のルールに従ってcaseラベルが適用されないが、スイッチ・ブロックに関連付けられたdefaultラベルがある場合は、defaultラベルが適用されます。
単一のcaseラベルには、複数のcase定数を含めることができます。 ラベルは、その定数のいずれかがセレクタ式の値と等しい場合、セレクタ式の値に適用されます。 たとえば、次のコードでは、列挙変数dayが次に示す列挙定数のいずれかである場合に、caseラベルが適用されます。
switch (day) {
...
case SATURDAY, SUNDAY :
System.out.println("It's the weekend!");
break;
...
}
caseパターンを持つcaseラベルが適用されるのは、パターンに対して値に一致するパターン・プロセスが成功したためです(§14.30.2)。 値がパターンと正常に一致すると、パターン・マッチングのプロセスは、パターンによって宣言されたパターン変数を初期化します。
CおよびC++では、switch文の本体を文にすることができ、caseラベルを持つ文は、その文によってすぐに含める必要はありません。 単純なループを考えてみましょう。
for (i = 0; i < n; ++i) foo();
ここで、nは正であることがわかっています。 Duffのデバイスと呼ばれるトリックをCまたはC++で使用してループをアンロールできますが、これは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);
}
幸いにも、このトリックは広く認識または使用されてはいないようです。 さらに、今日ではあまり必要ではなくなりました。この種のコード変換は、適切に最新式の最適化コンパイラの領域にあります。
switch文のSwitchブロック
switchブロックの一般的なルール(§14.11.1)に加えて、switch文にはswitchブロックに関するその他のルールがあります。
拡張switch文は、(i)セレクタ式の型がchar、byte、short、int、Character、Byte、Short、Integer、Stringまたはenum型でないか、(ii)switchブロックに関連付けられているcaseパターンまたはnullリテラルがある文です。
switch文のswitchブロックについて、次のすべてを満たす必要があります。そうしないと、コンパイル時にエラーが発生します。
switchブロック内のすべてのswitchルール式は、文式です(§14.8)。
switch文は、switchブロック内の矢印(->)の右側に表示される式、つまりswitchルール式として使用できる式に関して、switch式とは異なります。 switch文では、switchルール式として使用できるのは文式のみですが、switch式では任意の式を使用できます(§15.28.1)。
switch文が拡張switch文の場合、その文は完全である必要があります(§14.11.1.1)。
Java SE 21より前は、switch文(およびswitch式)は、次の2つの方法で制限されていました。(i)セレクタ式の型が整数型(longを除く)、列挙型またはStringのいずれかに制限されていて、(ii)case nullラベルはサポートされていませんでした。 また、switch式とは異なり、switch文は完全である必要はありませんでした。 これは、switchラベルが適用されず、switch文が暗黙のうちに何も行わないバグの検出が困難な原因であることがよくあります。 たとえば:
enum E { A, B, C }
E e = ...;
switch (e) {
case A -> System.out.println("A");
case B -> System.out.println("B");
// No case for C!
}
Java SE 21では、caseパターンのサポートに加えて、前述のswitch文(およびswitch式)の2つの制限が、(i)任意の参照型のセレクタ式を許可し、(ii)nullリテラルを持つcaseラベルを許可するように緩和されました。 また、Javaプログラミング言語の設計者は、拡張されたswitch文がswitch式と一致し、すべてを網羅する必要があることを決定しました。 これは、多くの場合、簡単なdefaultラベルを追加することで実現されます。 たとえば、次の拡張switch文は完全ではありません。
Object o = ...;
switch (o) { // Error - non-exhaustive switch!
case String s -> System.out.println("A string!");
}
しかし、それは簡単に網羅的にすることができます。
Object o = ...;
switch (o) {
case String s -> System.out.println("A string!");
default -> {}
}
互換性のため、拡張されたswitch文ではないswitch文は完全には必要ありません。
switch文の実行
switch文は、最初にセレクタ式を評価することによって実行されます。 セレクタ式の評価が突然完了した場合、switch文全体も同じ理由で突然完了します。
セレクタ式の評価が正常に完了した場合、switchブロックに関連付けられたswitchラベルがセレクタ式の値(§14.11.1.2)に適用されるかどうかを判断することで、switch文の実行が続行されます。 次に、
どのスイッチ・ラベルが適用されるかを判断するプロセスが突然完了した場合、switch文全体も同じ理由で突然完了します。
スイッチラベルが適用されない場合は、次のいずれかが保持されます。
セレクタ式の値がnullの場合、NullPointerExceptionがスローされ、その理由でswitch文全体が突然完了します。
switch文が拡張switch文の場合、MatchExceptionがスローされ、その理由によりswitch文全体が突然完了します。
セレクタ式の値がnullでなく、switch文が拡張されたswitch文ではない場合、switch文全体が正常に完了します。
スイッチラベルが適用される場合、次のいずれかが保持されます。
switchルール式のswitchラベルである場合、switchルール式は必ずしも文式(§14.11.2)です。 文の式が評価されます。 評価が正常に完了すると、switch文は正常に完了します。 評価の結果が値の場合、それは破棄されます。
switchルール・ブロックのswitchラベルである場合、ブロックが実行されます。 このブロックが正常に完了すると、switch文は正常に完了します。
スイッチ・ルールthrow文のスイッチ・ラベルである場合、throw文が実行されます。
switchラベルが付いた文グループのswitchラベルである場合、switchラベルに続くswitchブロックのすべての文が順番に実行されます。 これらの文が正常に完了すると、switch文は正常に完了します。
それ以外の場合、switchブロックには、適用するswitchラベルの後に続く文がなく、switch文が正常に完了します。
switchブロックの文または式の実行が突然完了する場合、次のように処理されます。
例14.11.3-1 switch文のフォールスルー
switchラベルが適用され、switchラベルがswitchルール用である場合、switchラベルによって導入されたswitchルール式または文が実行され、それ以外は何も実行されません。 文グループ用のswitchラベルの場合、そのswitchラベルに続くswitchブロックのすべてのブロック文が、後続のswitchラベルの後に出現するものを含めて実行されます。 その効果は、CおよびC++でのように、文の実行が「ラベルをフォールスルー」できることです。
たとえば、次のプログラムの場合:
class TooMany {
static void howMany(int k) {
switch (k) {
case 1: System.out.print("one ");
case 2: System.out.print("too ");
case 3: System.out.println("many");
}
}
public static void main(String[] args) {
howMany(3);
howMany(2);
howMany(1);
}
}
各caseのコードが次のcaseのコードに分類されるswitchブロックが含まれます。 結果として、プログラムの出力は次のようになります。
many too many one too many
フォールスルーは判断しにくいバグの原因になる場合があります。 コードがこの方法でcaseからcaseに該当しない場合、break文を使用して、制御をいつ転送するかを指定したり、スイッチ・ルールをプログラムのように使用できます。
class TwoMany {
static void howMany(int k) {
switch (k) {
case 1: System.out.println("one");
break; // exit the switch
case 2: System.out.println("two");
break; // exit the switch
case 3: System.out.println("many");
break; // not needed, but good style
}
}
static void howManyAgain(int k) {
switch (k) {
case 1 -> System.out.println("one");
case 2 -> System.out.println("two");
case 3 -> System.out.println("many");
}
}
public static void main(String[] args) {
howMany(1);
howMany(2);
howMany(3);
howManyAgain(1);
howManyAgain(2);
howManyAgain(3);
}
}
このプログラムは次の内容を出力します:
one two many one two many
while文
while文は、「式」の値がfalseになるまで、「式」および「文」を繰り返し実行します。
while ( 式 ) StatementNoShortIf
「式」のタイプはbooleanまたはBooleanである必要があります。そうしないと、コンパイル時にエラーが発生します。
while文は、最初に式を評価することによって実行されます。 結果のタイプがBooleanの場合、ボックス化解除変換の対象となります(§5.1.8)。
式または後続のボックス化解除変換(ある場合)の評価がなんらかの理由で突然完了した場合、while文も同じ理由で突然完了します。
それ以外の場合は、結果の値に基づいて実行が続行されます。
値がtrueの場合は、含まれている文が実行されます。 次の選択肢があります。
文が正常に実行されると、式の再評価から開始して、while文全体が再度実行されます。
文の実行が突然完了した場合は、§14.12.1を参照してください。
式の(ボックス化されていない可能性がある)値がfalseの場合、それ以上のアクションは実行されず、while文は正常に完了します。
式の(ボックス化されていない可能性がある)値が、最初に評価されるときにfalseである場合、文は実行されません。
while文の突然の完了
含まれている文の突然の完了は、次の方法で処理されます:
ラベルのないbreakが原因で文の実行が突然完了した場合、それ以上のアクションは実行されず、while文は正常に完了します。
ラベルのないcontinueが原因で文の実行が突然完了すると、while文全体が再度実行されます。
ラベルがLのcontinueのため、文の実行が突然完了した場合、次の選択肢があります。
while文にラベルLがある場合、while文全体が再度実行されます。
while文にラベルLがない場合は、ラベルLのcontinueのため、while文が突然完了します。
文の実行が他の理由で突然完了した場合、while文も同じ理由で突然完了します。
ラベル付きのbreakによる異常終了の場合、ラベル付き文の一般規則によって処理されます(§14.7)。
do文
do文は、「式」の値がfalseになるまで、「文」および「式」を繰り返し実行します。
「式」のタイプはbooleanまたはBooleanである必要があります。そうしないと、コンパイル時にエラーが発生します。
do文は、最初に文を実行することによって実行されます。 次の選択肢があります。
do文を実行すると、常に、含まれている文が少なくとも1回実行されます。
do文の突然の完了
含まれている文の突然の完了は、次の方法で処理されます:
ラベルのないbreakが原因で文の実行が突然完了した場合、それ以上のアクションは実行されず、do文は正常に完了します。
ラベルのないcontinueのために文の実行が突然完了すると、式が評価されます。 次に、結果の値に基づいて選択肢があります。
値がtrueの場合、do文全体が再度実行されます。
値がfalseの場合、これ以上のアクションは実行されず、do文は正常に完了します。
ラベルがLのcontinueのため、文の実行が突然完了した場合、次の選択肢があります。
do文にラベルLがある場合は、式が評価されます。 次の選択肢があります。
「式」の値がtrueの場合、do文全体が再度実行されます。
「式」の値がfalseの場合、それ以上のアクションは実行されず、do文が正常に完了します。
do文にラベルLがない場合は、ラベルLのcontinueのため、do文が突然完了します。
文の実行が他の理由で突然完了した場合、do文も同じ理由で突然完了します。
ラベル付きのbreakによる異常終了の場合、ラベル付き文の一般規則によって処理されます(§14.7)。
例14.13-1. do文
次のコードは、クラスIntegerのtoHexStringメソッドの実装の1つです。
public static String toHexString(int i) {
StringBuffer buf = new StringBuffer(8);
do {
buf.append(Character.forDigit(i & 0xF, 16));
i >>>= 4;
} while (i != 0);
return buf.reverse().toString();
}
少なくとも1つの数字を生成する必要があるため、do文は適切な制御構造です。
for文
for文には次の2つの形式があります。
基本的なfor文。
拡張されたfor文
for文
基本的なfor文では、いくつかの初期化コードが実行された後、式、文および一部の更新コードが、式の値がfalseになるまで繰り返し実行されます。
for ( [ForInit] ; [Expression] ; [ForUpdate] ) Statement
for ( [ForInit] ; [Expression] ; [ForUpdate] ) StatementNoShortIf
「式」のタイプはbooleanまたはBooleanである必要があります。そうしないと、コンパイル時にエラーが発生します。
基本的なfor文のForInit部分で宣言されたローカル変数のスコープとシャドウイングは、§6.3および§6.4で指定されています。
ネストされたクラスまたはインタフェースまたはラムダ式からの基本for文のForInit部分で宣言されたローカル変数への参照は、§6.5.6.1で指定されているように制限されます。
for文の初期化
for文は、最初にForInitコードを実行することによって実行されます。
for文の反復
次に、for反復ステップを次のように実行します。
式が存在する場合は評価されます。 結果のタイプがBooleanの場合、ボックス化解除変換の対象となります(§5.1.8)。
式または後続のボックス化解除変換(存在する場合)の評価が突然完了すると、for文も同じ理由で突然完了します。
それ以外の場合は、「式」の有無と、「式」が存在する場合は結果の値に基づいて選択が行われます。次の箇条書きを参照してください。
式が存在しないか、その式が存在し、その評価の結果の値(ボックス化解除を含む)がtrueの場合、含まれている文が実行されます。 次の選択肢があります。
文が正常に実行されると、次の2つのステップが順番に実行されます。
まず、ForUpdate部分が存在する場合は、式が左から右に順番に評価され、値が存在する場合は破棄されます。 いずれかの式の評価がなんらかの理由で突然完了した場合、for文は同じ理由で突然完了します。突然完了した式の右側にあるForUpdate文式は評価されません。
ForUpdate部分が存在しない場合、アクションは実行されません。
次に、別のfor反復ステップを実行します。
文の実行が突然完了した場合は、§14.14.1.3を参照してください。
「式」が存在し、その評価の結果の値(ボックス化解除の可能性を含む)がfalseの場合、それ以上のアクションは実行されず、for文は正常に完了します。
式の(ボックス化されていない可能性がある)値が、最初に評価されるときにfalseである場合、文は実行されません。
式が存在しない場合、for文が正常に完了できる唯一の方法は、break文を使用することです。
for文の突然の完了
含まれている文の突然の完了は、次の方法で処理されます:
ラベルのないbreakが原因で文の実行が突然完了した場合、それ以上のアクションは実行されず、for文は正常に完了します。
ラベルのないcontinueが原因で文の実行が突然完了した場合は、次の2つのステップが順番に実行されます。
まず、ForUpdate部分が存在する場合は、式が左から右に順番に評価され、値が存在する場合は破棄されます。
ForUpdate部分が存在しない場合、アクションは実行されません。
次に、別のfor反復ステップを実行します。
ラベルがLのcontinueのため、文の実行が突然完了した場合、次の選択肢があります。
for文にラベルLがある場合は、次の2つのステップが順番に実行されます。
まず、ForUpdate部分が存在する場合は、式が左から右に順番に評価され、値が存在する場合は破棄されます。
ForUpdateが存在しない場合、アクションは実行されません。
次に、別のfor反復ステップを実行します。
for文にラベルLがない場合は、ラベルLのcontinueのため、for文が突然完了します。
文の実行が他の理由で突然完了した場合、for文も同じ理由で突然完了します。
ラベル付きのbreakが原因で突然完了した場合、ラベル付き文の一般ルール(§14.7)によって処理されます。
for文
拡張されたfor文の形式は次のとおりです。
for ( LocalVariableDeclaration : 式 ) 文
for ( LocalVariableDeclaration : 式 ) StatementNoShortIf
便宜上、§4.3、§8.3、§8.4.1、および §14.4の次のプロダクションがここに示されています。
final
var
_
式の型は、配列型(§10.1)またはRAW型Iterableのサブタイプである必要があります。そうしないと、コンパイル時にエラーが発生します。
拡張されたfor文のヘッダーは、VariableDeclaratorIdで指定された識別子の名前を持つローカル変数を宣言するか、名前のないローカル変数を宣言します(§6.3)。 拡張されたfor文が実行されると、ローカル変数はループの反復ごとに、Iterableの連続する要素または式によって生成される配列に初期化されます。
拡張for文のヘッダーで宣言されたローカル変数のルールは、§14.4で指定され、LocalVariableTypeがvarの場合に適用されるその項のルールは無視されます。 さらに、次のすべてが当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します。
VariableDeclaratorListは、単一のVariableDeclaratorで構成されます。
VariableDeclaratorにはイニシャライザがありません。
LocalVariableTypeがvarの場合、VariableDeclaratorIdにカッコのペアはありません。
拡張for文のヘッダーで宣言されたローカル変数のスコープおよびシャドウ化は、§6.3および§6.4で指定されています。
ネストされたクラスまたはインタフェース、またはラムダ式からのローカル変数への参照は、§6.5.6.1で指定されているように制限されています。
拡張for文のヘッダーで宣言されたローカル変数の型Tは、次のように決定されます。
LocalVariableTypeがUnannTypeで、UnannTypeまたはVariableDeclaratorIdにカッコのペアが表示されない場合、TはUnannTypeで示されるタイプです。
LocalVariableTypeがUnannTypeで、カッコのペアがUnannTypeまたはVariableDeclaratorIdに指定されている場合、Tは§10.2で指定されます。
LocalVariableTypeがvarの場合は、次のようにRを式のタイプから導出します:
式に配列タイプがある場合、Rは配列タイプのコンポーネント・タイプです。
それ以外の場合、式にIterable<X>のサブタイプである型があり、X型の場合、RはXです。
それ以外の場合、式にはRAW型Iterableのサブタイプである型があり、RはObjectです。
Tは、R (§4.10.5)で言及されているすべての合成型変数に関する Rの上方投影です。
拡張されたfor文の正確な意味は、次のように基本的なfor文に変換することで得られます。
式のタイプがIterableのサブタイプである場合、基本的なfor文の形式は次のようになります。
for (I #i = Expression.iterator(); #i.hasNext(); ) { {VariableModifier} T VarDeclId = (TargetType) #i.next(); Statement }
説明:
式の型がIterable<X>のサブタイプで、一部の型引数Xの場合、Iはjava.util.Iterator<X>型です。 それ以外の場合、IはRAW型java.util.Iteratorです。
#iは、拡張されたfor文が発生した時点でスコープ(§6.3)内にある他の識別子(自動生成またはそれ以外)と区別される、自動生成された識別子です。
{VariableModifier}は、拡張for文のヘッダーで指定されています。
Tは、前述のローカル変数の型です。
Tが参照型の場合、TargetTypeはTです。 それ以外の場合、TargetTypeは、Iの型引数の取得変換(§5.1.10)の上限、またはIがRAWの場合はObjectです。
ヘッダー内のローカル変数の宣言に識別子が含まれている場合、VarDeclIdはヘッダーで指定される識別子として定義されます。それ以外の場合、VarDeclIdは_ (アンダースコア)として定義されます。
それ以外の場合、式には必ず配列型S[]があり、基本的なfor文には次の形式があります。
S[]#a = Expression;L1:L2: ...Lm: for (int #i = 0; #i < #a.length; #i++) { {VariableModifier} T VarDeclId = #a[#i]; Statement }
説明:
L1 ... Lmは、拡張されたfor文の直前の(空の)ラベルのシーケンスです。
#aおよび#iは、拡張されたfor文が発生した時点でスコープ内にある他の識別子(自動生成またはそれ以外)とは異なる、自動的に生成される識別子です。
{VariableModifier}は、拡張for文のヘッダーで指定されています。
Tは、前述のローカル変数の型です。
ヘッダー内のローカル変数の宣言に識別子が含まれている場合、VarDeclIdはヘッダーで指定される識別子として定義されます。それ以外の場合、VarDeclIdは_ (アンダースコア)として定義されます。
たとえば、次のコードがあるとします。
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);
}
break文
break文は、包含文から制御を転送します。
break [識別子] ;
break文には、次の2種類があります。
ラベルのないbreak文。
ラベルが「識別子」のbreak文。
ラベルなしのbreak文は、最も内側のswitch、while、doまたはfor文に制御を転送しようとします。この内側の文は、ブレーク・ターゲットと呼ばれ、すぐに正常に完了します。
ラベルIdentifierを持つbreak文は、ラベルと同じIdentifierを持つ包含ラベル付き文(§14.7)に制御を転送しようとします。その後、この包含文はブレーク・ターゲットと呼ばれ、すぐに正常に完了します。 この場合、ブレーク・ターゲットはswitch文、while文、do文またはfor文である必要はありません。
break文にブレーク・ターゲットがない場合、コンパイル時にエラーが発生します。
ブレーク・ターゲットに、break文を囲むメソッド、コンストラクタ、インスタンス・イニシャライザ、静的イニシャライザ、ラムダ式またはswitch式が含まれている場合は、コンパイル時にエラーが発生します。 つまり、非ローカル・ジャンプはありません。
ラベルなしのbreak文の実行は、常に突然完了します。理由は、ラベルなしのbreakです。
ラベルが「識別子」のbreak文の実行は、常に突然完了します。理由は、ラベルが「識別子」のbreakです。
これにより、break文が常に突然完了することが確認できます。
前述の説明では、tryブロックまたはcatch句にbreak文が含まれているブレーク・ターゲット内にtry文(§14.20)がある場合、制御がブレーク・ターゲットに転送される前に、それらのtry文のfinally句が順番に、最も内側から最も外側に順番に実行されるため、単に制御の転送ではなく、制御の転送が試行されます。 finally句が突然完了すると、break文によって開始された制御の転送が中断される可能性があります。
例14.15-1. break文
次の例では、数学的グラフは配列の配列で表されます。 グラフは、ノードのセットとエッジのセットで構成されます。各エッジは、あるノードから別のノード、またはノードからそのノードを指す矢印です。 この例では、冗長エッジがないことを前提としています。つまり、PおよびQという2つのノード(QはPと同じ場合もある)の場合、PからQまでのエッジは最大1つあります。
ノードは整数で表され、配列参照edges[がi][j]ArrayIndexOutOfBoundsExceptionをスローしないiおよびjごとに、ノードiからノードedges[までのエッジがあります。
i][j]
loseEdgesメソッド(整数iおよびj)のタスクは、指定されたグラフをコピーして新しいグラフを作成することです。ただし、ノードiからノードj(ある場合)にエッジを省略し、ノードjからノードi(ある場合)にエッジを省略します。
class Graph {
int[][] edges;
public Graph(int[][] edges) { this.edges = edges; }
public Graph loseEdges(int i, int j) {
int n = edges.length;
int[][] newedges = new int[n][];
for (int k = 0; k < n; ++k) {
edgelist:
{
int z;
search:
{
if (k == i) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == j) break search;
}
} else if (k == j) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == i) break search;
}
}
// No edge to be deleted; share this list.
newedges[k] = edges[k];
break edgelist;
} //search
// Copy the list, omitting the edge at position z.
int m = edges[k].length - 1;
int[] ne = new int[m];
System.arraycopy(edges[k], 0, ne, 0, z);
System.arraycopy(edges[k], z+1, ne, z, m-z);
newedges[k] = ne;
} //edgelist
}
return new Graph(newedges);
}
}
2つの文ラベルedgelistおよびsearchの使用と、break文の使用に注意してください。 これにより、1つのエッジを省略してリストをコピーするコードを、2つの個別のテスト間で共有できます。つまり、ノードiからノードjへのエッジのテストと、ノードjからノードiへのエッジのテストです。
continue文
continue文は、while文、do文またはfor文でのみ発生し、これらの3種類の文は反復文と呼ばれます。 制御は、反復文のループ継続ポイントに渡されます。
continue [識別子] ;
continue文には、次の2種類があります。
ラベルのないcontinue文。
ラベルが「識別子」のcontinue文。
ラベルなしのcontinue文は、最も内側のwhile、doまたはfor文に制御を転送しようとします。この内側の文はcontinue targetと呼ばれ、現在の反復をただちに終了し、新しい反復を開始します。
ラベルがIdentifierのcontinue文は、ラベルと同じIdentifierを持つ包含ラベル付き文(§14.7)に制御を転送しようとします。この包含文は、continue targetと呼ばれ、すぐに現在の反復を終了し、新しい反復を開始します。 この場合、続行ターゲットはwhile、doまたはfor文である必要があります。そうしないと、コンパイル時にエラーが発生します。
continue文にcontinueターゲットがない場合、コンパイル時にエラーが発生します。
continueターゲットに、continue文を囲むメソッド、コンストラクタ、インスタンス・イニシャライザ、静的イニシャライザ、ラムダ式またはswitch式が含まれている場合、コンパイル時にエラーが発生します。 つまり、非ローカル・ジャンプはありません。
ラベルなしのcontinue文の実行は、常に突然完了します。理由は、ラベルなしのcontinueです。
ラベルが「識別子」のcontinue文の実行は、常に突然完了します。理由は、ラベルが「識別子」のcontinueです。
これにより、continue文が常に突然完了することが確認できます。
continueによる異常終了の処理については、while文(§14.12)、do文(§14.13)およびfor文(§14.14)の説明を参照してください。
前述の説明では、tryブロックまたはcatch句にcontinue文が含まれているcontinueターゲット内にtry文(§14.20)がある場合、制御がcontinueターゲットに転送される前に、それらのtry文のfinally句が順番に最内から最外まで実行されます。 finally句が突然完了すると、continue文によって開始された制御の転送が中断される可能性があります。
例14.16-1. continue文
§14.15のGraphクラスでは、break文の1つを使用して、最も外側のforループの本体全体の実行を終了します。 forループ自体にラベルが付いている場合、このブレークはcontinueに置き換えることができます。
class Graph {
int[][] edges;
public Graph(int[][] edges) { this.edges = edges; }
public Graph loseEdges(int i, int j) {
int n = edges.length;
int[][] newedges = new int[n][];
edgelists:
for (int k = 0; k < n; ++k) {
int z;
search:
{
if (k == i) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == j) break search;
}
} else if (k == j) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == i) break search;
}
}
// No edge to be deleted; share this list.
newedges[k] = edges[k];
continue edgelists;
} //search
// Copy the list, omitting the edge at position z.
int m = edges[k].length - 1;
int[] ne = new int[m];
System.arraycopy(edges[k], 0, ne, 0, z);
System.arraycopy(edges[k], z+1, ne, z, m-z);
newedges[k] = ne;
} //edgelists
return new Graph(newedges);
}
}
どちらを使用するかは、主にプログラミング・スタイルの問題です。
return文
return文は、メソッド(§8.4、§15.12)またはコンストラクタ(§8.8、§15.9)の起動者に制御を戻します。
return [式] ;
return文には、次の2種類があります。
値を指定しないreturn文。
値Expressionを持つreturn文。
return文は、最も内側のコンストラクタ、メソッドまたはラムダ式の実行者に制御を転送しようとします。この内側の宣言または式は、リターン・ターゲットと呼ばれます。 値Expressionを持つreturn文の場合、Expressionの値が呼出しの値になります。
return文にリターン・ターゲットがない場合は、コンパイル時にエラーが発生します。
戻りターゲットに、(i) return文を包含するインスタンスまたは静的イニシャライザ、または(ii) return文を包含するswitch式が含まれている場合は、コンパイル時にエラーが発生します。
値のないreturn文の戻りターゲットがメソッドであり、そのメソッドがvoidとして宣言されていない場合、コンパイル時にエラーが発生します。
return文の戻りターゲットがコンストラクタで、return文がこのコンストラクタのプロローグ(§8.8.7)に出現した場合、コンパイル時にエラーが発生します。
値Expressionを持つreturn文の戻りターゲットがコンストラクタまたはvoidと宣言されたメソッドのいずれかである場合、コンパイル時にエラーが発生します。
値Expressionを持つreturn文の戻りターゲットが、宣言された戻り型Tを持つメソッドであり、ExpressionのタイプがTとの代入互換性(§5.2)でない場合、コンパイル時にエラーが発生します。
値を指定しないreturn文の実行は、常に突然完了します。これは、値を指定しない戻り値の理由です。
値「式」を持つreturn文を実行すると、最初に「式」が評価されます。 式の評価がなんらかの理由で突然完了した場合、return文はその理由で突然完了します。 式の評価が正常に完了し、値Vが生成されると、return文が突然完了します。これは、値Vの戻り値の理由です。
これにより、return文が常に突然完了することが確認できます。
前述の説明では、tryブロックまたはcatch句にreturn文が含まれているメソッドまたはコンストラクタ内にtry文(§14.20)がある場合、それらのtry文のfinally句は、メソッドまたはコンストラクタの起動元に制御が転送される前に、最も内側から外側まで順番に実行されます。 finally句が突然完了すると、return文によって開始された制御の転送が中断される可能性があります。
throw文
throw文を使用すると、例外(§11 (例外))がスローされます。 この結果は、複数の文および複数のコンストラクタ、インスタンス・イニシャライザ、静的イニシャライザおよびフィールド・イニシャライザの評価、およびスローされた値を捕捉するtry文(§14.20)が見つかるまでメソッドの起動を終了する可能性がある制御(§11.3)の即時転送です。 そのようなtry文が見つからない場合、throwを実行したスレッド(§17 (Threads and Locks))の実行は、スレッドが属するスレッド・グループのuncaughtExceptionメソッドの呼出し後に終了します(§11.3)。
throw 式 ;
throw文の式は、Throwable型に代入可能な参照型(§5.2)の変数または値を示すか、null参照を示すか、コンパイル時にエラーが発生します。
式の参照型は、常にクラス型になります(インタフェース型がThrowableに割り当て可能でないため)。パラメータ化されません(Throwableのサブクラスを汎用にできないため(§8.1.2)。
次の3つの条件のうち少なくとも1つがtrueである必要があり、そうでない場合はコンパイル時にエラーが発生します。
throw文がスローできる例外型は、§11.2.2で指定します。
throw文では、最初に式が評価されます。 次に、
式の評価がなんらかの理由で突然完了した場合、throwはその理由で突然完了します。
式の評価が正常に完了し、null以外の値Vが生成されると、throw文が突然完了します。これは、値Vを持つthrowの理由です。
式の評価が正常に完了し、null値が生成される場合、クラスNullPointerExceptionのインスタンスV'が作成され、nullのかわりにスローされます。 次に、throw文が突然完了します。これは、値がV'のthrowである理由です。
これにより、throw文が常に突然完了することが確認できます。
tryブロックにthrow文が含まれているtry文(§14.20)が囲まれている場合、これらのtry文のfinally句は、制御が外部に転送され、スローされた値が捕捉されるまで実行されます。 finally句が突然完了すると、throw文によって開始された制御の転送が中断される可能性があることに注意してください。
throw文がメソッド宣言またはラムダ式に含まれているが、その値がそれを含むtry文によって捕捉されない場合は、throwのためにメソッドの呼出しが突然完了します。
throw文がコンストラクタ宣言に含まれているが、その値がそれを含むtry文によって捕捉されない場合、コンストラクタを起動したクラス・インスタンス作成式は、throw (§15.9.4)のために突然完了します。
throw文が静的イニシャライザ(§8.7)に含まれている場合、コンパイル時チェック(§11.2.3)により、その値が常に未チェックの例外であるか、その値がそれを含むtry文によって常に捕捉されることが保証されます。 実行時に、このチェックにもかかわらず、throw文を含む一部のtry文によって値が捕捉されない場合、値がクラスErrorまたはそのサブクラスの1つのインスタンスである場合、値が再スローされます。それ以外の場合は、ExceptionInInitializerErrorオブジェクトにラップされ、その後スローされます(§12.4.2)。
throw文がインスタンス・イニシャライザ(§8.6)に含まれている場合、コンパイル時チェック(§11.2.3)により、その値が常に未チェックの例外であるか、その値がその値を含むtry文によって常に捕捉されるか、スローされた例外のタイプ(またはそのいずれかのスーパークラス)がクラスのすべてのコンストラクタのthrows句で発生します。
通常、ユーザー宣言のスロー可能型は、クラスThrowable (§11.1.1)のサブクラスであるクラスExceptionのサブクラスとして宣言する必要があります。
synchronized文
synchronized文は、実行スレッドにかわって相互排他ロック(§17.1)を取得し、ブロックを実行し、ロックを解放します。 実行中のスレッドがロックを所有している間は、ほかのスレッドがロックを取得することはできません。
式のタイプは参照型である必要があります。そうでない場合、コンパイル時にエラーが発生します。
synchronized文は、最初に式を評価することによって実行されます。 次に、
式の評価がなんらかの理由で突然完了した場合、synchronized文も同じ理由で突然完了します。
それ以外の場合は、「式」の値がnullの場合は、NullPointerExceptionがスローされます。
それ以外の場合は、式のnull以外の値をVにします。 実行中のスレッドは、Vに関連付けられたモニターをロックします。 その後、ブロックが実行され、次の選択肢があります:
ブロックの実行が正常に完了すると、モニターはロック解除され、synchronized文は正常に完了します。
なんらかの理由でブロックの実行が突然完了した場合、モニターはロック解除され、synchronized文も同じ理由で突然完了します。
synchronized文によって取得されるロックは、synchronizedメソッドによって暗黙的に取得されるロックと同じです(§8.4.3.6)。 1つのスレッドがロックを複数回取得することがあります。
オブジェクトに関連付けられたロックを取得しても、他のスレッドがオブジェクトのフィールドにアクセスしたり、オブジェクトのsynchronized解除メソッドを呼び出したりすることを妨げられることはありません。 他のスレッドでは、synchronizedメソッドまたはsynchronized文を従来の方法で使用して相互の除外を実現することもできます。
例14.19-1. synchronized文
class Test {
public static void main(String[] args) {
Test t = new Test();
synchronized(t) {
synchronized(t) {
System.out.println("made it!");
}
}
}
}
このプログラムは出力を生成します:
made it!
1つのスレッドがモニターを複数回ロックすることが許可されていない場合、このプログラムはデッドロックになることに注意してください。
try文
try文はブロックを実行します。 値がスローされ、try文にそれを捕捉できるcatch句が1つ以上ある場合、制御は最初のそのようなcatch句に転送されます。 try文にfinally句がある場合、tryブロックが正常に完了するか突然完了するかに関係なく、catch句が最初に制御されるかどうかに関係なく、別のコード・ブロックが実行されます。
catch ( CatchFormalParameter ) ブロック
finally ブロック
UnannClassTypeについては、§8.3を参照してください。 ここでは、便宜上、§4.3、§8.3、および §8.4.1からの次のプロダクションを示します。
キーワードtryの直後のブロックは、try文のtryブロックと呼ばれます。
キーワードfinallyの直後のブロックは、try文のfinallyブロックと呼ばれます。
try文には、例外ハンドラとも呼ばれるcatch句を含めることができます。
catch句は、例外パラメータと呼ばれるパラメータを1つのみ宣言します。
finalが例外パラメータ宣言の修飾子として複数回出現する場合は、コンパイル時にエラーが発生します。
例外パラメータのスコープとシャドウは、§6.3および §6.4で指定されています。
ネストされたクラスまたはインタフェース、またはラムダ式からの例外パラメータへの参照は、§6.5.6.1で指定されているように制限されています。
例外パラメータは、その型を単一のクラス型または2つ以上のクラス型(代替と呼ばれる)の和集合として表すことができます。 結合の代替方法は、構文的に|で区切ります。
例外パラメータが単一のクラス型として示されるcatch句は、uni-catch句と呼ばれます。
例外パラメータが型の結合として示されるcatch句は、マルチcatch句と呼ばれます。
例外パラメータの型を示すために使用される各クラス型は、クラスThrowableまたはThrowableのサブクラスである必要があります。そうでない場合、コンパイル時にエラーが発生します。
例外パラメータの型を示すために型変数が使用された場合、コンパイル時にエラーが発生します。
タイプの和集合に2つの代替DiおよびDj (i ≠ j)が含まれている場合、コンパイル時にエラーが発生します。ここで、DiはDjのサブタイプです(§4.10.2)。
単一クラス型を持つ型を示す例外パラメータの宣言型は、そのクラス型です。
D1 | D2 | ... | Dnがlub(D1、 D2、 ...、 Dn)の和集合としてその型を示す例外パラメータの宣言型。
マルチcatch句の例外パラメータは、明示的にfinalを宣言しない場合、暗黙的にfinalを宣言します。
finalを暗黙的または明示的に宣言した例外パラメータがcatch句の本体内に割り当てられている場合、コンパイル時にエラーが発生します。
uni-catch句の例外パラメータは、暗黙的にfinalを宣言されることはありませんが、明示的にfinalを宣言することも、事実上finalにすることもできます(§4.12.4)。
暗黙的にfinal例外パラメータは、その宣言によってfinalですが、事実上最終的な例外パラメータは、その使用方法によってfinalです。 マルチcatch句の例外パラメータは暗黙的にfinalとして宣言されるため、代入演算子の左側のオペランドとして発生することはありませんが、事実上最終とはみなされません。
例外パラメータが実質的にfinal (uni-catch句内)または暗黙的にfinal (複数catch句内)の場合、明示的なfinal修飾子を宣言に追加しても、コンパイル時エラーは発生しません。 一方、uni-catch句の例外パラメータが明示的にfinalとして宣言されている場合、final修飾子を削除すると、コンパイル時にエラーが発生する可能性があります。これは、この例外パラメータは事実上最終とみなされるようになり、catch句の本体内の匿名クラス宣言およびローカル・クラス宣言によって参照されなくなったためです。 コンパイル時のエラーがない場合は、catch句の本体で例外パラメータが再割当てされるようにプログラムをさらに変更できるため、事実上最終とみなされなくなります。
try文がスローできる例外型は、§11.2.2で指定します。
try文のtryブロックによってスローされ、try文のcatch句(ある場合)によって捕捉される例外の関係は、§11.2.3で指定されます。
例外ハンドラは左から右の順に考慮されます。可能なかぎり早いcatch句は例外を受け入れ、§11.3で指定されているように、スローされた例外オブジェクトを引数として受け取ります。
マルチcatch句は、UNI-catch句のシーケンスと考えることができます。 つまり、例外パラメータの型が共用体D1|D2|...|Dnとして示されるcatch句は、例外パラメータの型がクラス型D1、D2、...、Dnであるn句のシーケンスと同等です。 各n catch句のBlockでは、例外パラメータの宣言された型はlub(D1、D2、...、Dn)です。 たとえば、次のコードを実行したとします:
try {
... throws ReflectiveOperationException ...
}
catch (ClassNotFoundException | IllegalAccessException ex) {
... body ...
}
は、次のコードと意味的に同等です。
try {
... throws ReflectiveOperationException ...
}
catch (final ClassNotFoundException ex1) {
final ReflectiveOperationException ex = ex1;
... body ...
}
catch (final IllegalAccessException ex2) {
final ReflectiveOperationException ex = ex2;
... body ...
}
ここで、2つの代替を持つmulti-catch句は、2つのuni-catch句(代替ごとに1つ)に変換されています。 Javaコンパイラは、この方法でコードを複製してマルチcatch句をコンパイルする必要も推奨もありません。これは、複製せずにclassファイルでマルチcatch句を表現できるためです。
finally句を使用すると、制御がtryブロックまたはcatchブロックをどのように残すかにかかわらず、finallyブロックがtryブロックおよび実行される可能性のあるcatchブロックの後に実行されます。 finallyブロックの処理はかなり複雑であるため、finallyブロックの有無にかかわらずtry文の2つのケースについて個別に説明します(§14.20.1、§14.20.2)。
try文は、catch句およびfinally句がtry-with-resources文(§14.20.3)の場合は省略できます。
try-catchの実行
finallyブロックのないtry文は、最初にtryブロックを実行することによって実行されます。 次の選択肢があります。
tryブロックの実行が正常に完了した場合、それ以上のアクションは実行されず、try文が正常に完了します。
値Vのthrowが原因でtryブロックの実行が突然完了した場合、次の選択肢があります。
Vの実行時型が、try文のcatch句の捕捉可能な例外クラス(§5.2)と互換性がある代入である場合、そのようなcatch句が最初に(左端)選択されます。 値Vは、選択したcatch句のパラメータに割り当てられ、そのcatch句のブロックが実行され、次の選択肢があります。
このブロックが正常に完了すると、try文は正常に完了します。
なんらかの理由でそのブロックが突然完了した場合、try文も同じ理由で突然完了します。
Vの実行時型が、try文のcatch句の捕捉可能な例外クラスと互換性がない場合、try文は、値Vのthrowのために突然完了します。
他の理由でtryブロックの実行が突然完了した場合、try文も同じ理由で突然完了します。
例14.20.1-1 例外の取得
class BlewIt extends Exception {
BlewIt() { }
BlewIt(String s) { super(s); }
}
class Test {
static void blowUp() throws BlewIt { throw new BlewIt(); }
public static void main(String[] args) {
try {
blowUp();
} catch (RuntimeException r) {
System.out.println("Caught RuntimeException");
} catch (BlewIt b) {
System.out.println("Caught BlewIt");
}
}
}
ここでは、例外BlewItがメソッドblowUpによってスローされます。 mainの本体にあるtry-catch文には、2つのcatch句があります。 例外の実行時型はBlewItで、RuntimeException型の変数には代入できませんが、BlewIt型の変数には代入可能であるため、この例の出力は次のようになります。
Caught BlewIt
try-finallyおよびtry-catch-finallyの実行
finallyブロックを含むtry文は、最初にtryブロックを実行することによって実行されます。 次の選択肢があります。
tryブロックの実行が正常に完了すると、finallyブロックが実行され、次の選択肢があります。
finallyブロックが正常に完了すると、try文は正常に完了します。
finallyブロックが理由Sで突然完了した場合、try文は理由Sのために突然完了します。
値Vのthrowが原因でtryブロックの実行が突然完了した場合、次の選択肢があります。
Vの実行時型が、try文の任意のcatch句の捕捉可能な例外クラスと互換性がある場合、このようなcatch句が最初(左端)に選択されます。 値Vは、選択したcatch句のパラメータに割り当てられ、そのcatch句のブロックが実行されます。 次の選択肢があります。
catchブロックが正常に完了すると、finallyブロックが実行されます。 次の選択肢があります。
finallyブロックが正常に完了すると、try文は正常に完了します。
finallyブロックがなんらかの理由で突然完了した場合、try文も同じ理由で突然完了します。
Rが原因でcatchブロックが突然完了すると、finallyブロックが実行されます。 次の選択肢があります。
finallyブロックが正常に完了すると、try文がRのために突然完了します。
finallyブロックが理由Sのために突然完了した場合、try文は理由Sのために突然完了します(理由Rは破棄されます)。
Vの実行時型が、try文のcatch句の捕捉可能な例外クラスと互換性がない場合、finallyブロックが実行されます。 次の選択肢があります。
finallyブロックが正常に完了すると、値Vのthrowのためにtry文が突然完了します。
finallyブロックが理由Sで突然完了した場合、try文は理由Sのために突然完了します(また、値Vのthrowは破棄され、忘れられます)。
tryブロックの実行がRのその他の理由で突然完了した場合、finallyブロックが実行され、次の選択肢があります。
finallyブロックが正常に完了すると、try文がRのために突然完了します。
finallyブロックが理由Sのために突然完了した場合、try文は理由Sのために突然完了します(理由Rは破棄されます)。
例14.20.2-1 finallyでの捕捉されない例外の処理
class BlewIt extends Exception {
BlewIt() { }
BlewIt(String s) { super(s); }
}
class Test {
static void blowUp() throws BlewIt {
throw new NullPointerException();
}
public static void main(String[] args) {
try {
blowUp();
} catch (BlewIt b) {
System.out.println("Caught BlewIt");
} finally {
System.out.println("Uncaught Exception");
}
}
}
このプログラムは出力を生成します:
Uncaught Exception
Exception in thread "main" java.lang.NullPointerException
at Test.blowUp(Test.java:7)
at Test.main(Test.java:11)
メソッドblowUpによってスローされるNullPointerException (RuntimeExceptionの一種)は、mainのtry文では捕捉されません。これは、NullPointerExceptionがBlewIt型の変数に代入できないためです。 これにより、finally句が実行され、その後、テスト・プログラムの唯一のスレッドであるmainを実行するスレッドが、捕捉されない例外のために終了し、通常、例外名と単純なバックトレースが出力されます。 ただし、この仕様ではバックトレースは必要ありません。
バックトレースの必須化の問題は、プログラム内のある時点で例外を作成し、あとでスローできることです。 スタック・トレースが実際にスローされないかぎり、スタック・トレースを例外に格納することは非常にコストがかかります(この場合、スタックをアンワインド中にトレースが生成される可能性があります)。 したがって、すべての例外でバックトレースを要求することはありません。
try- リソースあり
try-with-resources文は、tryブロックの実行前に初期化され、自動的にクローズされる変数(リソースと呼ばれる)を使用して、tryブロックの実行後、初期化元の順序とは逆の順序でパラメータ化されます。catch句およびfinally句は、多くの場合、リソースが自動的にクローズされるときに不要です。
try ResourceSpecification Block [Catches] [Finally]
( ResourceList [;] )
便宜上、§4.3、§8.3、§8.4.1、および §14.4の次のプロダクションがここに示されています。
final
var
_
UnannTypeについては、§8.3を参照してください。
リソース仕様は、イニシャライザ式でローカル変数を宣言するか、既存の変数を参照することによって、try-with-resources文のリソースを示します。 既存の変数は、式名(§6.5.6)またはフィールド・アクセス式(§15.11)によって参照されます。
リソース仕様で宣言されたローカル変数の規則は、§14.4で規定されています。 さらに、次のすべてが当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します。
VariableDeclaratorListは、単一のVariableDeclaratorで構成されます。
VariableDeclaratorにはイニシャライザがあります。
VariableDeclaratorIdには、カッコのペアがありません。
リソース仕様で宣言されたローカル変数のスコープとシャドウは、§6.3および §6.4で指定されています。
ネストされたクラスまたはインタフェース、またはラムダ式からのローカル変数への参照は、§6.5.6.1で指定されているように制限されています。
リソース仕様で宣言されたローカル変数の型は、§14.4.1に記載されています。
リソース仕様で宣言されたローカル変数の型、またはリソース仕様で参照される既存の変数の型は、AutoCloseableのサブタイプである必要があります。そうでないと、コンパイル時にエラーが発生します。
リソースの指定で、同じ名前のローカル変数を複数宣言すると、コンパイル時にエラーが発生します。
リソース仕様は、複数の無名ローカル変数(§6.1)を宣言することができる。
リソースはfinalです。その意味は次のとおりです。
リソース仕様で宣言されたローカル変数は、明示的にfinal (§4.12.4)と宣言されていない場合、暗黙的にfinalと宣言されます。
リソース仕様で参照される既存の変数は、try-with-resources文(§16 (Definite Assignment))の前に確実に割り当てられるfinalまたは事実上final変数である必要があります。そうしないと、コンパイル時にエラーが発生します。
リソースは左から右の順序で初期化されます。 リソースの初期化に失敗する(つまり、そのイニシャライザ式で例外がスローされる)場合、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文が存在し、全体的な変換が完了します。
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文は、この項のrestに従って変換されます。
次の形式の基本的なtry-with-resources文の意味:
try ({VariableModifier} R VariableDeclaratorId = Expression ...) Block
ローカル変数宣言および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}として定義されます(存在する場合)。
VariableDeclaratorIdが識別子の場合、Identifierはその識別子として定義されます。それ以外の場合、Identifierは、try-with-resources文が発生した時点でスコープ内にある他の識別子(自動生成またはその他の方法)と異なる、自動生成された識別子として定義されます。
#t、#primaryExcおよび#suppressedExcは、try-with-resources文が発生した時点でスコープ内にある他の識別子(自動生成またはそれ以外)と異なる、自動的に生成される識別子です。
リソース指定が1つのリソースを示している場合、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のthrowが原因でリソースの初期化が突然完了した場合、値Vのthrowが原因でtry-with-resources文が突然完了します。
リソースの初期化が正常に完了し、値Vのthrowが原因でtryブロックが突然完了した場合は、次のようになります。
リソースの自動クローズが正常に完了した場合、値Vのthrowにより、try-with-resources文が突然完了します。
値V2のthrowが原因でリソースの自動クローズが突然完了した場合、値Vのthrowが原因でtry-with-resources文が突然完了し、V2が抑制された例外リストVに追加されます。
リソースの初期化が正常に完了し、tryブロックが正常に完了し、値Vのthrowのためにリソースの自動クローズが突然完了した場合、値Vのthrowのためにtry-with-resources文が突然完了します。
複数のリソースを管理する基本的なtry-with-resources文の場合:
値Vのthrowが原因でリソースの初期化が突然完了した場合、次のようになります。
正常に初期化されたすべてのリソースの自動クローズ(おそらくゼロ)が正常に完了した場合、try-with-resources文は、値Vのthrowのために突然完了します。
値V1...Vnのthrowが原因で、正常に初期化されたすべてのリソースの自動クローズ(ゼロの可能性あり)が突然完了した場合、try-with-resources文は、値Vのthrowのために突然完了し、残りの値V1...VnがVの抑制された例外リストに追加されます。
すべてのリソースの初期化が正常に完了し、値Vのthrowが原因でtryブロックが突然完了した場合、次のようになります。
すべての初期化済リソースの自動クローズが正常に完了した場合、try-with-resources文は、値Vのthrowのために突然完了します。
値V1...Vnのthrowが原因で、1つ以上の初期化されたリソースの自動クローズが突然完了した場合、try-with-resources文は、値Vのthrowのために突然完了し、残りの値V1...VnがVの抑制された例外リストに追加されます。
すべてのリソースの初期化が正常に完了し、tryブロックが正常に完了した場合は、次のようになります。
値Vのthrowが原因で初期化済リソースの1つの自動クローズが突然完了し、初期化済リソースの他のすべての自動クローズが正常に完了した場合、値Vのthrowが原因でtry-with-resources文が突然完了します。
値V1...Vnのthrowが原因で、初期化済リソースの複数の自動クローズが突然完了した場合(V1は右端のリソースからの例外で、クローズに失敗し、Vnは例外です)左端のリソースからのクローズに失敗すると、try-with-resources文は、値V1のthrowのために突然完了し、残りの値V2...VnがV1の抑制された例外リストに追加されます。
try
少なくとも1つのcatch句またはfinally句(あるいはその両方)を含むtry-with-resources文は、拡張try-with-resources文と呼ばれます。
拡張try-with-resources文の意味は次のとおりです。
try ResourceSpecification Block [Catches] [Finally]
次の変換によって、try-catch文、try-finally文またはtry-catch-finally文内にネストされた基本のtry-with-resources文に変換されます。
try {
try ResourceSpecification
Block
}
[Catches]
[Finally]
変換の効果は、リソース指定をtry文内に置くことです。 これにより、拡張try-with-resources文のcatch句で、リソースの自動初期化またはクローズが原因で例外を捕捉できます。
さらに、finallyキーワードの意図に従って、finallyブロックが実行されるまでにすべてのリソースがクローズ(またはクローズ)されます。
yield文
yield文では、囲んでいるswitch式(§15.28)によって指定された値が生成されるため、制御が転送されます。
yield 式 ;
yield文は、最も内側のswitch式に制御を転送しようとします。この内側の式は、イールド・ターゲットと呼ばれ、すぐに正常に完了し、式の値がswitch式の値になります。
yield文にyieldターゲットがない場合、コンパイル時にエラーが発生します。
yield文を囲むメソッド、コンストラクタ、インスタンス・イニシャライザ、静的イニシャライザまたはラムダ式がyieldターゲットに含まれている場合は、コンパイル時にエラーが発生します。 つまり、非ローカル・ジャンプはありません。
yield文の式が無効(§15.1)の場合、コンパイル時にエラーが発生します。
yield文を実行すると、最初に式が評価されます。 式の評価がなんらかの理由で突然完了した場合、yield文はその理由で突然完了します。 式の評価が正常に完了し、値Vが生成されると、yield文が突然完了します。この理由は、値Vを持つ結果です。
これにより、yield文が常に突然完了することが確認できます。
例14.21-1. yield文
次の例では、yield文を使用して、囲んでいるswitch式の値を生成します。
class Test {
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
SATURDAY, SUNDAY
}
public int calculate(Day d) {
return switch (d) {
case SATURDAY, SUNDAY -> d.ordinal();
default -> {
int len = d.toString().length();
yield len*len;
}
};
}
}
文が到達不能であるため実行できない場合、コンパイル時にエラーが発生します。
この項では、"到達可能"という言葉を正確に説明します。 それは、文を含むコンストラクタ、メソッド、インスタンス・イニシャライザまたは静的イニシャライザの先頭からその文自体までの実行パスが存在する必要があるという意味です。 分析では文の構造が考慮されます。 条件式が定数値trueを持つwhile文、do文およびfor文の特別な処理を除き、式の値はフロー分析では考慮されません。
たとえば、Javaコンパイラでは、次のコードが受け入れられます。
{
int n = 5;
while (n > 7) k = 2;
}
nの値はコンパイル時に認識されていても、原則として、kへの割当ては実行できないことをコンパイル時に認識できます。
この項で示すルールでは、次の2つの技術用語の意味を明らかにします。
文が到達可能かどうか
文が正常に完了できるかどうか
ルールでは、文は、それに到達可能である場合のみ、正常完了可能です。
さらに2つの技術用語を使用します。
到達可能なbreak文は、ブレーク・ターゲット内に、tryブロックにbreak文が含まれているtry文がない場合、またはtryブロックにbreak文が含まれ、それらのtry文のすべてのfinally句が正常に完了できるtry文がある場合、文を終了します。
この定義は、§14.15の「制御の転送試行」に関するロジックに基づいています。
continue文は、do文内に、tryブロックにcontinue文が含まれているtry文がない場合、またはtryブロックにcontinue文が含まれ、それらのtry文のすべてのfinally句が正常に完了できるtry文がある場合、do文を続行します。
この規則は次のとおりです。
コンストラクタ、メソッド、インスタンス・イニシャライザ、静的イニシャライザ、ラムダ式またはswitch式の本体であるブロックに到達できます。
switchブロックではない空のブロックは、それに到達可能な場合は、正常完了可能です。
switchブロックではない、またはコンストラクタ呼出しを含むコンストラクタ本体ではない空でないブロックは、その中の最後の文が正常に完了できる場合に、通常どおり完了できます。
switchブロックではない、またはコンストラクタ呼出しを含むコンストラクタ本体ではない、空でないブロック内の最初の文は、そのブロックが到達可能な場合に到達可能です。
switchブロックではない、またはコンストラクタ呼出しを含むコンストラクタ本体ではない、空でないブロック内の他のすべての文Sは、Sの前の文が正常に完了できる場合に到達可能です。
コンストラクタ呼出しを含むコンストラクタの本体である空でないブロックは、その中の最後の文が正常に完了できる場合に、通常どおり完了できます。
コンストラクタ呼出しを含むコンストラクタ本体の空でないプロローグ内の最初の文は、ブロックが到達可能な場合に到達可能です。
コンストラクタの呼出しを含むコンストラクタ本体のプロローグ内の他のすべての文Sは、Sの前の文が正常に完了できる場合に到達可能です。
コンストラクタの起動と空のprologueを含むコンストラクタの空でないepilogueの最初の文は、ブロックが到達可能であれば到達可能です。
コンストラクタの呼出しと空でないプロローグを含むコンストラクタの空でないエピローグ内の最初の文は、コンストラクタのプロローグの最後の文が正常に完了できる場合に到達可能です。
コンストラクタの呼出しを含むコンストラクタのエピローグにある他のすべての文Sは、Sの前の文が正常に完了できる場合に到達可能です。
ローカル・クラス宣言文は、到達可能な場合は正常に完了できます。
ローカル変数宣言文は、到達可能な場合は通常完了します。
空の文は、到達可能な場合は通常完了します。
ラベル付きステートメントは、次のうち少なくとも1つに当てはまる場合に正常に完了することがあります。
含まれる文は、正常に完了できます。
ラベル付き文を終了する、到達可能なbreak文があります。
ラベル付きステートメントが到達可能な場合、含まれているステートメントは到達可能です。
式文は、到達可能な場合は通常どおり完了できます。
if-then文は、到達可能な場合は正常に完了できます。
if-then文がアクセス可能であれば、then文にアクセスできます。
then文が正常に完了できる場合、またはelse文が正常に完了できる場合は、if-then-else文が正常に完了できます。
if-then-else文がアクセス可能であれば、then文にアクセスできます。
if-then-else文がアクセス可能であれば、else文にアクセスできます。
このif文の処理は、else部分があるかどうかに関係なく、かなり異常です。 根拠は、このセクションの最後に示されます。
assert文は、到達可能な場合は正常に完了できます。
switchブロックが空であるか、switchラベルのみが含まれているswitch文が正常に完了できます。
switchブロックがswitchラベル付き文グループで構成されているswitch文は、次のうち少なくとも1つに該当する場合に、通常どおり完了できます。
switchブロックの最後の文が正常に完了する可能性があります。
最後のswitchブロック文グループの後に、switchラベルが少なくとも1つあります。
switch文を終了する、到達可能なbreak文があります。
switch文が拡張されておらず(§14.11.2)、switchブロックにdefaultラベルが含まれていません。
switchブロックがswitchルールで構成されているswitch文は、次のうち少なくとも1つに該当する場合に、通常どおり完了できます。
switchルールの1つには、switchルール式(必ずしも文式)が導入されています。
switchルールの1つに、正常に完了できるswitchルール・ブロックが導入されています。
switchルールの1つに、switch文を終了する到達可能なbreak文を含むswitchルール・ブロックが導入されています。
switch文が拡張されておらず(§14.11.2)、switchブロックにdefaultラベルが含まれていません。
switchブロックは、そのswitch文にアクセス可能な場合に到達可能です。
switchブロックのラベル付き文グループで構成されるswitchブロック内の文は、switchブロックが到達可能であり、次のうち少なくとも1つに該当する場合に到達可能です。
caseまたはdefaultラベルが付きます。
switchブロックの前には文があり、前の文が正常に完了できます。
スイッチブロックが到達可能な場合、スイッチブロック内のスイッチルールブロックは到達可能です。
switchブロックが到達可能な場合、switchブロック内のswitchルールthrow文にアクセスできます。
while文は、次のうち少なくとも1つがtrueの場合、通常どおりに完了できます。
while文が到達可能で、条件式が値trueを持つ定数式(§15.29)ではありません。
while文を終了する、到達可能なbreak文があります。
while文が到達可能であり、条件式がfalseの値を持つ定数式でない場合、含まれる文が到達可能です。
do文は、次のうち少なくとも1つがtrueの場合、通常どおりに完了できます。
含まれる文は正常に完了でき、条件式は値trueを持つ定数式(§15.29)ではありません。
do文にはラベルなしの到達可能なcontinue文が含まれ、do文は、そのcontinue文を含む最も内側のwhile、doまたはfor文であり、continue文はそのdo文を続行し、条件式は値trueの定数式ではありません。
do文には、Lというラベルが付いた到達可能なcontinue文が含まれ、do文にはLというラベルが付いており、continue文はそのdo文を続行し、条件式はtrueという値を持つ定数式ではありません。
do文を終了する、到達可能なbreak文があります。
do文がアクセス可能であれば、含まれる文にアクセスできます。
基本的なfor文は、次のうち少なくとも1つに該当する場合に、通常どおり完了できます。
for文が到達可能であり、条件式があり、条件式が値trueを持つ定数式(§15.29)ではありません。
for文を終了する、到達可能なbreak文があります。
for文が到達可能であり、条件式がfalseの値を持つ定数式でない場合、含まれる文が到達可能です。
拡張されたfor文は、到達可能な場合は正常に完了できます。
break文、continue文、return文、throw文またはyield文が正常に完了できません。
含まれている文が正常に完了できる場合は、synchronized文が正常に完了できます。
synchronized文がアクセス可能であれば、含まれる文にアクセスできます。
次の両方に該当する場合は、try文が正常に完了できます。
tryブロックは、正常に完了することも、任意のcatchブロックが正常に完了することもできます。
try文にfinallyブロックがある場合、finallyブロックは正常に完了できます。
tryブロックは、try文が到達可能な場合に到達可能です。
次の両方に該当する場合は、catchブロックCにアクセス可能です。
catchブロックのブロックは、catchブロックにアクセス可能な場合に到達可能です。
finallyブロックが存在する場合、try文がアクセス可能であればアクセス可能です。
if文が次の方法で処理されることを想定している場合があります。
if-then文は、次のうち少なくとも1つに該当する場合に、通常どおり完了できます。
if-then文が到達可能であり、条件式は、値がtrueの定数式ではありません。
then文は正常に完了できます。
if-then文が到達可能であり、条件式がfalseの値を持つ定数式でない場合、then文に到達できます。
then文が正常に完了できる場合、またはelse文が正常に完了できる場合は、if-then-else文が正常に完了できます。
if-then-else文が到達可能であり、条件式がfalseの定数式でない場合、then文に到達できます。
if-then-else文が到達可能であり、条件式がtrueの定数式でない場合、else文に到達できます。
この方法は、他の制御構造の治療と一致します。 ただし、条件付きコンパイルの目的でif文を便利に使用できるようにするために、実際のルールは異なります。
例として、次の文を実行するとコンパイル時にエラーが発生します。
while (false) { x=3; }
文x=3;はアクセスできませんが、表面的には次のような場合です。
if (false) { x=3; }
コンパイル時にエラーが発生しません。 最適化コンパイラでは、文x=3;は実行されず、生成されたclassファイルからその文のコードを省略することを選択できますが、ここで指定する技術的な意味では、文x=3;は「到達不能」とはみなされません。
この異なる処置の根拠は、プログラマが次のようなフラグ変数を定義できるようにすることです。
static final boolean DEBUG = false;
次に、次のようなコードを記述します。
if (DEBUG) { x=3; }
DEBUGの値をfalseからtrueに変更するか、trueからfalseに変更してから、プログラム・テキストに他の変更を加えずにコードを正しくコンパイルすることが可能であるという考え方があります。
条件付きコンパイルには注意事項があります。 "flag"変数を使用する一連のクラス(より正確には、static定数変数(§4.12.4)がコンパイルされ、条件付きコードが省略された場合、フラグの定義を含む新しいバージョンのクラスまたはインタフェースのみを配布しても、後から十分ではありません。 このフラグを使用するクラスは、その新しい値を表示しないため、その動作は驚くかもしれません。 基本的に、フラグの値の変更は、既存のバイナリとのバイナリ互換性がありますが(LinkageErrorは発生しません)、動作互換性はありません。
static定数変数の値をインライン化するもう1つの理由は、switch文が原因です。 これらは、定数式に依存する唯一の種類の文です。つまり、switch文の各caseラベルは、値が他のすべてのcaseラベルと異なる定数式である必要があります。caseラベルは、多くの場合、static定数変数を参照するため、すべてのラベルに異なる値があることはすぐに明らかではない可能性があります。 コンパイル時に重複するラベルがないことが判明した場合は、classファイルに値をインライン化することで、実行時に重複するラベル(非常に望ましいプロパティ)がないことが保証されます。
例14.22-1. 条件付きコンパイル
この例の場合:
class Flags { static final boolean DEBUG = true; }
class Test {
public static void main(String[] args) {
if (Flags.DEBUG)
System.out.println("DEBUG is true");
}
}
コンパイルおよび実行され、次の出力が生成されます。
DEBUG is true
新しいバージョンのクラスFlagsが作成されるとします。
class Flags { static final boolean DEBUG = false; }
Flagsが再コンパイルされたがTestではない場合、Testの既存のバイナリを使用して新しいバイナリを実行すると、次の出力が生成されます。
DEBUG is true
DEBUGはstatic定数変数であるため、その値はクラスFlagsを参照せずにTestをコンパイルするために使用できます。
この動作は、変更された例のように、Flagsがインタフェースであった場合にも発生します。
interface Flags { boolean DEBUG = true; }
class Test {
public static void main(String[] args) {
if (Flags.DEBUG)
System.out.println("DEBUG is true");
}
}
実際、インタフェースのフィールドは常にstaticおよびfinalであるため、インタフェースのフィールドには定数式のみを割り当てることをお薦めします。 インタフェースのプリミティブ型のフィールドが変更される可能性がある場合、その値は次のように自動的に表現される可能性があることに注意してください。
interface Flags {
boolean debug = Boolean.valueOf(true).booleanValue();
}
この値が定数式でないことを確認します。 他のプリミティブ型にも同様の慣用句が存在します。
パターンは、値に対して実行できるテストを示します。 パターンは文および式のオペランドとして出現し、テストする値を提供します。 パターンでは、0個以上のローカル変数(パターン変数とも呼ばれる)が宣言されます。
パターンに対して値をテストするプロセスは、パターン一致と呼ばれます。 値がパターンに正常に一致した場合、パターン・マッチングのプロセスは、パターンによって宣言されたパターン変数(ある場合)を初期化します。
パターン変数は、パターン一致が成功し、パターン変数が初期化されるスコープ(§6.3)にのみ存在します。 初期化されていないパターン変数を使用することはできません。
型パターンは、値がパターンに出現する型のインスタンスかどうかをテストするために使用されます。 レコード・パターンは、値がレコード・クラス・タイプのインスタンスであるかどうか、およびある場合はレコード・コンポーネント値に対してパターン一致を再帰的に実行するかどうかをテストするために使用されます。
_
便宜上、§4.3、§8.3、§8.4.1、および §14.4の次のプロダクションがここに示されています。
final
var
_
UnannTypeについては、§8.3を参照してください。
(1)パターンがレコード・パターンのコンポーネント・パターン・リストに直接表示される場合、または(2)パターンがレコード・パターンのコンポーネント・パターン・リストに直接表示されるレコード・パターンにネストされている場合、パターンはレコード・パターンにネストされます。 パターンは、レコード・パターンにネストされていない場合、最上位レベルです。
型パターンは、パターン変数として知られる1つのローカル変数を宣言します。 宣言に識別子が含まれている場合は、パターン変数の名前を指定します。それ以外の場合は、パターン変数を名前のないパターン変数と呼びます。
型パターンで宣言されたローカル変数の規則は、§14.4で規定されています。 さらに、次のすべてが当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します。
トップ・レベル型パターンのLocalVariableTypeは、参照型を示します(さらに、varではありません)。
VariableDeclaratorListは、単一のVariableDeclaratorで構成されます。
VariableDeclaratorにはイニシャライザがありません。
VariableDeclaratorIdには、カッコのペアがありません。
トップ・レベル型パターンで宣言されるパターン変数の型は、LocalVariableTypeで示される参照型です。
ネストされた型パターンで宣言されたパターン変数の型は、次のように決定されます。
LocalVariableTypeがUnannTypeの場合、パターン変数の型はUnannTypeで示されます。
LocalVariableTypeがvarの場合、型パターンがレコード・パターンのコンポーネント・パターン・リストに直接表示される必要があります。そうしないと、コンパイル時にエラーが発生します。
Rをレコード・パターンのタイプにし、TをRの対応するコンポーネント・フィールドのタイプにします(§8.10.3)。 パターン変数の型は、Tが示すすべての合成型変数に対する Tの上方投影です。
次のレコード・クラスの宣言について考えます:
record R<T>(ArrayList<T> a){}
レコード・パターンがR<String>(var b)の場合、パターン変数bの型はArrayList<String>です。
型パターンは、R型のレコード・パターンのコンポーネント・パターン・リストに直接表示され、Rの対応するレコード・コンポーネントのタイプがUで、型パターンがU型(§14.30.3)に対して無条件である場合、null一致と見なされます。
この型パターンのコンパイル時プロパティは、パターン・マッチングの実行時プロセス(§14.30.2)で使用されるため、実行時に使用する型パターンに関連付けられます。
レコード・パターンは、ReferenceTypeと、コンポーネント・パターン(存在する場合)を含むコンポーネント・パターン・リストで構成されます。 ReferenceTypeがレコード・クラス・タイプ(§8.10)でない場合、コンパイル時にエラーが発生します。
ReferenceTypeがRAW型の場合、§18.5.5の説明に従って、レコード・パターンの型が推測されます。 レコード・パターンに対して型を推論できない場合、コンパイル時にエラーが発生します。
ReferenceType (またはその一部)に注釈が付けられている場合は、コンパイル時にエラーが発生します。
Javaプログラミング言語の将来のバージョンでは、この注釈に対する制限が解除される可能性があります。
それ以外の場合、レコード・パターンのタイプはReferenceTypeです。
レコード・パターンのコンポーネント・パターン・リストの長さは、ReferenceTypeで指定されたレコード・クラスの宣言のレコード・コンポーネント・リストの長さと同じである必要があります。そうしないと、コンパイル時にエラーが発生します。
レコード・パターンでは、パターン変数自体を直接宣言しませんが、コンポーネント・パターン・リスト内のパターン変数の宣言を含めることができます。
レコード・パターンに同じ名前のパターン変数の宣言が複数含まれていると、コンパイル時にエラーが発生します。
match-allパターンは、パターン変数を宣言しない特別なパターンであり、レコード・パターンrのコンポーネント・パターン・リストにのみ直接出現できます。
Rをレコード・パターンrのタイプとし、TをRの対応するコンポーネント・フィールドのタイプにします(§8.10.3)。 match-allパターンの型は、Tが示すすべての合成型変数に対する Tの上向き投影です。
完全一致パターンは、名前なしパターン変数を宣言し、LocalVariableTypeがvarであるネストされた型パターンと同等であることがわかります。
パターン・マッチングは、実行時にパターンに対して値をテストするプロセスです。 パターン一致は、文の実行(§14.1)および式の評価(§15.1)とは異なります。 値がパターンに正常に一致する場合、パターン・マッチング・プロセスが、パターンによって宣言されたすべてのパターン変数(ある場合)を初期化します。
パターン照合のプロセスには、式の評価または文の実行が含まれる場合があります。 したがって、式の評価または文の実行が突然完了した場合、パターン一致は突然完了と呼ばれます。 突然の完了には常に関連する理由があります。これは常に、指定された値を持つthrowです。 パターン・マッチングは、突然完了しない場合、正常に完了と呼ばれます。
値がパターンと一致するかどうかを決定するルールおよびパターン変数を初期化するルールは、次のとおりです。
null参照は、型パターンがnull一致(§14.30.1)の場合は型パターンを一致し、そうでない場合は一致しません。
null参照が一致する場合は、型パターンによって宣言されたパターン変数がnull参照に初期化されます。
null参照が一致しない場合、型パターンによって宣言されたパターン変数は初期化されません。
null参照ではない値vは、vがClassCastExceptionを発生させずにターゲット型Tに変換(§5.7)をテストすることで変換できる場合にT型の型パターンを一致し、それ以外の場合は一致しません。
vが一致する場合、型パターンで宣言されたパターン変数はvに初期化されます。
vが一致しない場合、型パターンで宣言されたパターン変数は初期化されません。
NULL参照はレコード・パターンに一致しません。
この場合、レコード・パターンに含まれる宣言に示される任意のパターン変数は初期化されません。
null参照ではない値vは、タイプがRのレコード・パターンとコンポーネント・パターン・リストLを一致します(i) vは、ClassCastExceptionを発生させずにターゲット・タイプRへの変換(§5.7)をテストすることで変換できる場合、および(ii) vの各レコード・コンポーネントがLの対応するコンポーネント・パターンと一致し、それ以外の場合は一致しない場合。
vの各レコード・コンポーネントは、そのコンポーネントに対応するvのアクセッサ・メソッドを起動することによって決定されます。 アクセッサ・メソッドの呼出しの実行が理由Sで突然完了した場合、原因SでMatchExceptionをスローすることで、パターン一致が突然完了します。
レコード・パターンのコンポーネント・パターン・リストに表示されるパターンで宣言されたパターン変数は、リスト内のすべてのパターンが一致する場合にのみ初期化されます。
すべての値が一致パターンを一致します。
次のいずれかのルールが当てはまる場合、パターンpは型Tで適用可能であるとみなされます。
パターンpは、型Tのすべての値がpと一致することをコンパイル時に決定できる場合、型Tの無条件であるとみなされるため、パターン一致の実行時テストの側面を省略できます。 次のように定義されます:
参照型Sのパターン変数を宣言する型パターンは、Tの消去がSの消去のサブタイプである場合、参照型Tに対して無条件です。
プリミティブ型Pのパターン変数を宣言する型パターンは、型Pに対して無条件です。
match-allパターンは、すべての型Tに対して無条件です。
null参照はレコード・パターンと一致しないため、無条件であるレコード・パターンはありません。
パターンpは、qに一致するすべての値がpにも一致し、次のように定義されている場合に、別のパターンqを支配します。
パターンpは、pがTに対して無条件である場合、T型のパターン変数を宣言する型パターンを支配します。
pがRに対して無条件である場合、パターンpはR型のレコード・パターンより優位です。
タイプがRでコンポーネント・パターン・リストがLのレコード・パターンは、タイプがSでコンポーネント・パターン・リストがMの別のレコード・パターンよりも優位です(i) RとSが同じレコード・クラスに名前を付け、(ii) Lのすべてのコンポーネント・パターン(存在する場合)がMの対応するコンポーネント・パターンよりも優位である場合に、そのレコード・パターンより優先されます。
pがTに対して無条件である場合、パターンpは型Tのmatch-allパターンより優位です。