目次 | 前 | 次 | 索引 | Java言語規定 第2版 |
プログラムの実行系列は,文(statement)によって制御される。文は効果を求めて実行され,値をもたない。
構造の一部として他の文を含む(contain)文もある。それらの他の文を副文とする。文 S が文 T を含み,文 T が文 U を含み,文 S 及び文 U と異なるような,文 T が存在しない場合に,文 S が文 U を直接含む(immediately contains)という。同じ方法で,その構造の一部として式(15)を含む文もある。
14.1では,文の正常完了と中途完了との区別を示す(14.1)。それ以降では,様々な種類の文についてそれらの正常な振る舞い及び中途完了に対する特別な処置を詳細に示す。
ブロックを最初に示し(14.2),続いて局所クラス宣言(14.3)と局所変数宣言文(14.4)を示す。
次に,熟知された"ぶら下がりelse
"問題を回避する文法処理(14.5)について示す。
C及びC++プログラマに身近な文としては,空文(14.6),ラベル付き文(14.7),式文(14.8),if
文(14.9),switch
文(14.10),while
文(14.11),do
文(14.12),for
文(14.13),break
文(14.14),continue
文(14.15),及びreturn
文(14.16)がある。
C及びC++と異なり,Javaプログラム言語はgoto
文をもたない。しかし,break
文及びcontinue
文は,文のラベルに言及できる。
C言語にないJavaプログラム言語の文には,throw
文(14.17),synchronized
文(14.18)及びtry
文(14.19)がある。
14.20は,すべての文が技術的な意味で到達可能(reachable)でなければならないという要求を示す。
すべてのステップが中途完了の表示なしに実行されれば,文は,正常完了(complete normally)したという。しかし,次のイベントが文の正常完了を妨げることもある。
break
文(14.14),continue
文(14.15),return
文(14.16)は,制御転送を引き起こし,それらを含む文の正常完了を妨げることがある。
throw
文(14.17)も,例外を発生する。例外は,制御転送を引き起こし,文の正常完了を妨げることがある。
中途完了は,常に次の内の一つの関連理由(reason)をもつ。
break
break
continue
continue
return
return
throw
throw
(14.17),実行時の例外又はエラー(11., 15.6)のいずれかによる例外の発生に限られる。文中で式が評価される場合,式の中途完了は常に即座に文の中途完了を同じ理由で引き起こす。正常実行モードにおける引き続いた実行ステップのすべては実行されない。
14.においては,特に指定しない限り,副文の中途完了は,文そのものの中途完了を即座に同じ理由で引き起こし,その文の正常実行モードにおける残りの実行ステップのすべては実行されない。
別途指定しない限り,すべての式の評価及びすべての副文の実行が正常完了すると,文も正常完了する。
Block: { BlockStatementsopt } BlockStatements: BlockStatement BlockStatements BlockStatement BlockStatement: LocalVariableDeclarationStatement ClassDeclaration Statementブロックの実行では,各局所変数宣言文及びその他の文は,順番に初めから終わりまで(左から右へ)実行される。これらすべてのブロック内の文が正常完了すると,ブロックも正常完了する。これらのうちの文のどれかが,何らかの理由で中途完了すると,同じ理由でブロックも中途完了する。
ブロック内で宣言された局所クラスの有効範囲は,直接取り囲むブロックの残りであり,それ自身のクラス宣言を含む。
局所クラス C の名前は,C の有効範囲内の,直接取り囲むメソッド,コンストラクタ,又は初期化子ブロックの局所クラスとして再宣言してはならない。再宣言するとコンパイル時エラーとなる。しかしながら,局所クラス宣言は,局所クラス宣言の有効範囲内で入れ子となったクラス宣言の内部ではおおい隠される(6.3.1)。局所クラスは正準名をもたないし,完全限定名ももたない。
局所クラス宣言が,public
,protected
,private
又は static
のアクセス修飾子のうち一つでも含むならば,コンパイル時エラーとする。
メソッド foo の最初の文では,局所クラス宣言がまだ有効範囲にないため,局所クラス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 }
Cyclic
のインスタンスではなく,メンバクラス Global.Cyclic
のインスタンスを作成する。
局所クラスの有効範囲が (その本体だけでなく) その宣言を包含するという事実は,局所クラス Cyclic
の定義が Global.Cyclic
ではなくそれ自身を拡張して,本当に循環的であることを意味する。その結果,局所クラス Cyclic
の宣言はコンパイル時エラーとなる。
局所クラスの名前は,同じメソッド (場合によっては,コンストラクタ又は初期化子) 内で再宣言することができないため,Local
の2番目と3番目の宣言はコンパイル時エラーとなる。しかしながら,Local
は,AnotherLocal
のような,他のもっと深く入れ子となったクラスの文脈で再宣言することができる。
Local
の4番目と最後の宣言は,それ以前の Local
の宣言の有効範囲の外にあるため,認められている。
LocalVariableDeclarationStatement: LocalVariableDeclaration ; LocalVariableDeclaration: finalopt Type VariableDeclaratorsここでの記述を更に明瞭にするために,8.3での記述をここで繰り返す。
VariableDeclarators: VariableDeclarator VariableDeclarators , VariableDeclarator VariableDeclarator: VariableDeclaratorId VariableDeclaratorId = VariableInitializer VariableDeclaratorId: Identifier VariableDeclaratorId [ ] VariableInitializer: Expression ArrayInitializerすべての局所変数宣言文は,ブロックに直接含まれる。局所変数宣言文はブロックにおける他の種類の文と自由に混在してよい。
局所変数宣言は,for
文(14.13)の先頭に置かれることもある。この場合,それが局所変数宣言文の一部であったかのように実行される。
宣言子の最初にオプションのキーワード final が現れると,宣言される変数は最終変数(4.5.4)となる。
変数の型は,局所変数宣言に現れるTypeによって指定される。型指定子の後には宣言子内のIdentifierが存在し,さらに角括弧対が続く。
したがって,次行の局所変数宣言は次次行以降の一連の宣言に相当する。
int a, b[], c[][];
C及びC++の伝統を尊重し,角括弧が宣言子内で利用できる。しかし,一般規則としては,次行の局所変数宣言が,次次行以降の一連の宣言に相当する。int a; int[] b; int[][] c;
float[][] f[][], g[][][], h[]; // Yechh!
これらの配列宣言の"混在した表記法"は望ましくない。float[][][][] f; float[][][][][] g; float[][][] h;
型 float
の局所変数は常に単精度数値集合(4.2.3)の要素を値として含む。同様に,型 double
の局所変数は常に倍精度数値集合の要素を値として含む。型 float
の局所変数が単精度数値集合の要素ではない単精度指数部拡張数値集合の要素を含んだり,型 double
の局所変数が倍精度数値集合の要素ではない倍精度指数部拡張数値集合の要素を含むことはどちらも許されない。
局所変数 v の名前は,v の有効範囲内の,直接取り囲むメソッド,コンストラクタ,又は初期化子ブロックの局所変数として再宣言してはならない。再宣言すると,コンパイル時エラーとなる。局所変数 v の名前は,v の有効範囲内の,直接取り囲むメソッド,コンストラクタ,又は初期化子ブロッックの try 文の catch 節の例外仮引数として再宣言してはならない。再宣言すると,コンパイル時エラーとなる。しかしながら,メソッド又は初期化子ブロックの局所変数は,局所変数の有効範囲内で入れ子となったクラス宣言の内部ではおおい隠される(6.3.1)。
局所変数は,限定名(6.6)で呼ぶことはできない。単純名だけが使用できる。
class Test { static int x; public static void main(String[] args) { int x = x; } }
x
の初期化が局所変数としての x
の宣言の有効範囲内にあり,局所変数 x
は,まだ値をもっておらず,使用不能なので,この例はコンパイル時エラーとなる。
これは,局所変数class Test { static int x; public static void main(String[] args) { int x = (x=2)*2; System.out.println(x); } }
x
がそれを使う以前に確実に代入されている(16.)ことによる。出力結果は次のとおりとなる。
他の例を次に示す。4
これは正しくコンパイルでき,出力は次のとおりになる。class Test { public static void main(String[] args) { System.out.print("2+1="); int two = 2, three = two + 1; System.out.println(three); } }
変数2+1=3
three
の初期化子は,その前の宣言子において宣言された変数 two
を正しく参照でき,次の行におけるメソッド呼出しは,ブロックの前の方で宣言された変数 three
を正しく参照できる。
for
文で宣言した局所変数の有効範囲は,それ自体の初期化子を含めたその for
文の残りとする。
同じメソッド,コンストラクタ,又は初期化子ブロック内の局所変数の識別子の宣言が,同じ名前の仮引数又は同名の局所変数の範囲内に現れたら,コンパイル時エラーが発生する。
この制限は,その他の非常に不明瞭なバグを検出するのに役立つ。局所変数によるメンバのおおい隠しに関する同様の制限は,上位クラスにおけるメンバの追加によって下位クラスの局所変数の名前を改めなければならないので,実用的でないと判断した。同様の考慮により,入れ子クラスのメンバによる局所変数のおおい隠し,又は入れ子クラス内で宣言された局所変数による局所変数のおおい隠しという制限は着目に値しない。したがって,次の例はエラーなしでコンパイルすることができる。class Test { public static void main(String[] args) { int i; for (int i = 0; i < 10; i++) System.out.println(i); } }
一方,同じ名前の局所変数を,互いに他を含まない二つの別々のブロック又はclass Test { public static void main(String[] args) { int i; class Local { { for (int i = 0; i < 10; i++) System.out.println(i); } } new Local(); } }
for
文において宣言してもよい。次に例を示す。
これはエラーなしでコンパイルでき,実行すると次の出力を生成する。class Test { public static void main(String[] args) { for (int i = 0; i < 10; i++) System.out.print(i + " "); for (int i = 10; i > 0; i--) System.out.print(i + " "); System.out.println(); } }
0 1 2 3 4 5 6 7 8 9 10 9 8 7 6 5 4 3 2 1
例えば,キーワード this
を用い,形式 this.x
によって,おおい隠されたフィールド x
にアクセスできる。実際,次の形式がコンストラクタ(8.8)に典型的に出現する。
この例では,コンストラクタは,初期化されるフィールドと同じ名前の仮引数をもつ。これは,仮引数ごとに異なる名前を生成しなければならない場合よりは簡単であって,この様式化された文脈においては,さほど混乱を生じない。しかし,一般には,フィールドと同じ名前を局所変数に割り当てることは,よい書き方ではない。class Pair { Object first, second; public Pair(Object first, Object second) { this.first = first; this.second = second; } }
第1番目を除いて,各初期化は,その前の初期化式の評価が正常完了した場合にだけ,実行される。局所変数宣言の実行は,最後の初期化式の評価が正常完了して初めて,正常完了する。局所変数宣言が初期化式を全く含まなければ,常にその実行は正常完了する。
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
文に属するものと勝手に決めてしまう。この規則は,次の文法で明らかにされている。
Statement: StatementWithoutTrailingSubstatement LabeledStatement IfThenStatement IfThenElseStatement WhileStatement ForStatement StatementWithoutTrailingSubstatement: Block EmptyStatement ExpressionStatement SwitchStatement DoStatement BreakStatement ContinueStatement ReturnStatement SynchronizedStatement ThrowStatement TryStatement StatementNoShortIf: StatementWithoutTrailingSubstatement LabeledStatementNoShortIf IfThenElseStatementNoShortIf WhileStatementNoShortIf ForStatementNoShortIf次に,ここでの記述を更に明瞭にするために,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
文禁止"という制限をもたない場合の,同じ種類の文の実行の振る舞いと同じとする。この区分は,文法上の困難さを解決するためだけに用いる。
EmptyStatement: ;空文の実行は,常に正常完了する。
LabeledStatement: Identifier : Statement LabeledStatementNoShortIf: Identifier : StatementNoShortIfここで Identifier は,直接含まれるStatement のラベルと宣言される。
C及びC++ と異なり,Javaプログラム言語は,goto
文をもっていない。文のラベル識別子は,ラベル付き文のどこにでも出現可能な,break
文 (14.14)又は continue
文(14.15)とともに使われる。
ラベル付き文により宣言されたラベルの有効範囲は,そのラベル付き文により直接取り囲まれた文とする。
l をラベルとし,l を直接取り囲むメソッド,コンストラクタ,インスタンス初期化子,又は静的初期化子を m とする。m の中で直接取り囲まれた他のラベルの宣言を,l がおおい隠せば(6.3.1),コンパイル時エラーとなる。
同じ識別子をラベルとして使い,パッケージ,クラス,インタフェース,メソッド,フィールド,仮引数又は局所変数の名前としても使うことに対する制限はない。文にラベル付けするために使用した識別子は,同じ名前をもつパッケージ,クラス,インタフェース,メソッド,フィールド,仮引数又は局所変数を不明瞭(6.3.2)にしない。クラス,インタフェース,メソッド,フィールド,局所変数又は例外ハンドラ(14.19) の仮引数として使用した識別子は,同じ名前をもつ文のラベルを不明瞭にしない。
ラベル付き文は,直接含まれた Statement の実行によって実行される。文が Identifier によってラベル付けされ,その含む Statement が同じ Identifier をもつ break
文によって中途完了すれば,ラベル付き文は正常完了する。Statement が他の理由で中途完了した場合には,ラベル付き文も同じ理由で中途完了する。
ExpressionStatement: StatementExpression ; StatementExpression: Assignment PreIncrementExpression PreDecrementExpression PostIncrementExpression PostDecrementExpression MethodInvocation ClassInstanceCreationExpression式文(expression statement)は,式を評価することによって実行される。式が値をもっても,その値は放棄される。式文の実行が正常完了するのは,式の評価が正常完了するのと等価とする。
C及びC++ と異なり,Javaプログラム言語では,限られた形の式だけが式文として使われる。Javaプログラム言語では void
という型はないので,"出力を型 void
にキャストする"という伝統的なCでの,次の式文の書き方は許されないことに注意する必要がある。
一方,Javaは,式文においてすべての最も有益な種類の式を許す。またJavaでは,型(void) ... ; // incorrect!
void
のメソッドを呼び出すために,式文としてメソッド呼出しを使う必要がない。したがって,前述の工夫は,ほとんど必要ない。この工夫が必要なら,値割当て文(15.26) 又は局所変数宣言文(14.4)のいずれかを,その代りに使用できる。 if
文if
文は,文の条件付きの実行,又は2文の条件付き選択の内,両方ではなく,どちらか一方だけの実行の選択を可能とする。
IfThenStatement: if ( Expression ) Statement IfThenElseStatement: if ( Expression ) StatementNoShortIf else Statement IfThenElseStatementNoShortIf: if ( Expression ) StatementNoShortIf else StatementNoShortIfExpression は,
boolean
型でなければならない。そうでなければ,コンパイル時エラーが発生する。if-then
文if-then
文の実行では,最初に Expression を評価する。Expressionの評価が何らかの理由で中途完了すると,if
-then
文も同じ理由で中途完了する。そうでなければ,式の評価の結果生じる値に基づいて選択が行われる。
true
ならば,含まれる Statement が実行される。if
-then
文の実行は,含まれる Statement の実行が正常完了したときに限り,正常完了する。
false
ならば,さらなる動作は起こらず,if
-then
文は正常完了する。
if-then-else
文if
-then
-else
文は,最初に Expression を評価することによって実行される。Expression の評価が何らかの理由で中途完了すれば,if
-then
-else
文も同じ理由で中途完了する。そうでなければ,式の評価の結果生じる値に基づいて,選択が行われる。
true
ならば,含まれる最初の Statement (キーワード else
の前にあるもの)が実行される。その文が正常完了する限り,if
-then
-else
文は正常完了する。
false
ならば,含まれる第2の Statement (キーワード else
の後ろにあるもの)が実行される。その文が正常完了する限り,if
-then
-else
文は正常完了する。
switch
文switch
文は,式の値に応じて幾つかの文のいずれかへ制御を移す。
SwitchStatement: switch ( Expression ) SwitchBlock SwitchBlock: { SwitchBlockStatementGroupsopt SwitchLabelsopt } SwitchBlockStatementGroups: SwitchBlockStatementGroup SwitchBlockStatementGroups SwitchBlockStatementGroup SwitchBlockStatementGroup: SwitchLabels BlockStatements SwitchLabels: SwitchLabel SwitchLabels SwitchLabel SwitchLabel: case ConstantExpression : default :Expression の型は,
char
,byte
,short
又は int
のいずれかでなければならない。そうでなければ,コンパイル時エラーが発生する。
switch
文の本体は switch ブロック(switch block)として知られる。switch ブロックに直接含まれる文は,一つ以上の case
ラベル又は default
ラベルによってラベル付けされてもよい。case
ラベルにおける定数式(15.28)の値と共に,これらのラベルは,switch
文に関連している(associated) という。
次のすべては,真でなければならない。そうでなければ,コンパイル時エラーが生じる。
switch
文に関連したすべての case
の定数式は,switch
の Expressionの型に代入可能(5.2)でなければならない 。
switch
文に関連した case
の定数式は,どの二つも同じ値をもってはならない。
switch
文に関連可能な default
ラベルは,最大一つだけとする。
switch
文の本体は文となることが可能で,case
ラベル付きの文がその文に直接含まれる必要はない。単純なループの例を考える。
ここでfor (i = 0; i < n; ++i) foo();
n
が正ということは分かっている。Duff の仕掛け(Duff's device) として知られる工夫が,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
文が実行されるときには,最初に Expression が評価される。Expression の評価が何らかの理由で中途完了すると,switch
文も同じ理由で中途完了する。そうでなければ,各 case
の定数と Expression の値との比較を実行する。それから,選択が行われる。
case
定数のうちのどれか一つが式の値と等しい場合,case
が合致したといい,switch ブロックの合致した case
ラベルの後のすべての文が,もし存在すれば,順に実行される。これらの文がすべて正常完了するか,又は合致した case
ラベルの後に文が存在しなければ,switch
文全体が正常完了する。
case
も合致しないが,default
ラベルが存在するときには,switch ブロックにおける default
ラベルの後のすべての文が,存在すれば,順に実行される。これらの文がすべて正常完了するか,default
ラベルの後に文がなければ,switch
文の全体が正常完了する。
case
が合致せず,default
ラベルのない場合,さらなる動作が何も起こらず,switch
文は正常完了する。
switch
文の Block 本体に直接含まれる文が中途完了した場合には,次のとおりに扱われる。
break
によって Statement の実行が中途完了した場合には,さらなる動作は起こらず,switch
文は正常完了する。
switch
文も同じ理由で中途完了する。ラベル付き break
による中途完了の場合は,ラベル付き文 (14.7) のための一般規則にしたがって扱われる。
各 case のコードが,次の case のコードへと落ちていくswitchブロックを含む。その結果,プログラムは次を出力する。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 へと落ちていくのを止めるには,次の例のようにmany too many one too many
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 } } public static void main(String[] args) { howMany(1); howMany(2); howMany(3); } }
one two many
while
文では,Expression の値が false
になるまで,Expression 及び Statement を繰り返し実行する。
WhileStatement: while ( Expression ) Statement WhileStatementNoShortIf: while ( Expression ) StatementNoShortIfExpression は
boolean
型でなければならない。そうでなければ,コンパイル時エラーが発生する。
while
文の実行では,最初に Expression が評価される。Expression の評価が何らかの理由で中途完了すれば,while
文も同じ理由で中途完了する。そうでなければ,得られる値に基づいて次の選択をする。
true
ならば,含まれる Statementが実行される。そのとき,次の選択が存在する。
while
文全体が再実行される。
false
ならば,さらなる動作は起こらず,while
文は正常完了する。
false
ならば,Statement は実行されない。
break
によって Statement の実行が中途完了した場合には,さらなる動作は起こらず while
文は正常完了する。
continue
によって Statement の実行が中途完了した場合には,while
文全体が再び実行される。
continue
文によって Statement の実行が中途完了した場合には,次の選択がある。
while
文も同じ理由で中途完了する。ラベル付き break
による中途完了の場合は,ラベル付き文の一般規則 (14.7) によって扱われることに注意せよ。
do
文では Expression の値が false
になるまで,Expression 及び Statement を繰り返し実行する。
DoStatement: do Statement while ( Expression ) ;Expression は
boolean
型でなければならない。そうでなければ,コンパイル時エラーが発生する。
do
文の実行では,最初に Statement が実行される。そのとき,次の選択が存在する。
do
文も同じ理由で中途完了する。そうでなければ,得られる値に基づく次の選択がある。
do
文の実行では,含まれる Statement は常に少なくとも 1 度は実行される。
break
で中途完了した場合には,さらなる動作は起こらず,do
文は正常完了する。
continue
で中途完了した場合には,Expression が評価される。その結果生じる値に基づいて選択を行う。
continue
のために中途完了すれば,次の選択がある。
do
文がラベル L をもてば,Expression が評価される。そのとき,次の選択がある。
do
文がラベル L をもっていない場合には,do
文はラベル L 付きの continue
で中途完了する。
do
文は,同じ理由で中途完了する。ラベル付き break
による中途完了の場合は,一般規則 (14.7) によって扱われる。
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(); }
do
文は適切な制御構造である。for
文for
文は,初期化コードを実行し,Expression の値が false
となるまで,Expression,Statement 及び更新のコードを繰返し実行する。
ForStatement: for ( ForInitopt ; Expressionopt ; ForUpdateopt ) Statement ForStatementNoShortIf: for ( ForInitopt ; Expressionopt ; ForUpdateopt ) StatementNoShortIf ForInit: StatementExpressionList LocalVariableDeclaration ForUpdate: StatementExpressionList StatementExpressionList: StatementExpression StatementExpressionList , StatementExpressionExpression は,
boolean
型でなければならない。そうでなければ,コンパイル時エラーが発生する。for
文の初期化for
文の実行は,最初に ForInit コードの実行によって行われる。
for
文も同じ理由で中途完了する。中途完了した式文の右側のいずれの ForInit 式文も評価されない。
for
文 (14.13)の ForInit 部で宣言された局所変数の有効範囲には次のものがすべて含まれる。局所変数宣言が何らかの理由によって中途完了した場合,
for
文は同じ理由で中途完了する。for
文の繰返しfor
文における繰返しは,次のとおりとする。
for
文は同じ理由で中途完了する。そうでなければ,Expression が存在するかどうか,及び Expression が存在すれば,その結果の値にもとづいた選択が存在する。
true
ならば,その中に含まれる Statement が実行される。さらにそこで次の選択が存在する。
for
文は同じ理由によって中途完了する。中途完了した文の右側のいずれの ForUpdate 文も評価されない。ForUpdate 部がなければ,何の動作も起こらない。
for
繰返し処理が実行される。
false
ならば,以降の処理は行われず,for
文は正常完了する。
false
であれば,Statement は実行されない。
Expression が存在しなければ,break
文を使用したときだけ,for
文は正常完了可能とする。
for
文の中途完了
break
文のために中途完了すれば,それ以降の処理は行われず,for
文は正常完了する。
continue
文のために中途完了すれば,次の二つの処理が順に実行される。
for
文の繰返しが行われる。
continue
文のために中途完了すれば,次の選択が存在する。
for
文がラベル L をもてば,次の二つの手順が順に行われる。
for
文の繰返しが行われる。
for
文がラベル L をもたなければ,その for
文はラベル L 付きの continue
文のために中途完了する。
break
文による中途完了の場合は,ラベル付き文の一般規則(14.7)によって扱われることに注意すること。
break
文break
文は,取り囲んでいる文の外側に制御を移す。
BreakStatement: break Identifieropt ;ラベルなしの
break
文は,直接取り囲んでいるメソッド又は初期化子ブロック内の,取り囲んでいる最も内側の switch
,while
,do
又は for
文に制御を移そうとする。この文は break ターゲット(break target)と呼ばれ,直ちに正常完了する。
正確には,ラベルなし break
文は,いつもラベルなし break
であるという理由で中途完了する。いかなる switch
,while
,do
又は for
文も,break
文を取り囲んでいなければ,コンパイル時エラーが発生する。
ラベル Identifier 付きの break
文は,それを取り囲んでいる,同じ Identifier をラベルとする,ラベル付き文(14.7)に制御を移そうとする。この文は break ターゲット(break target)と呼ばれ,直ちに正常完了する。この場合,break
ターゲットは,while
,do
,for
又は switch
文でなくてもよい。break
文は直接取り囲んでいるメソッド又は初期化子ブロック内のラベルを参照しなければならない。局所的でない制御の移動はない。
正確には,ラベル Identifier 付き break
文は,常にラベル Identifier 付き break
であるという理由で中途完了する。ラベルとして Identifier をもった,いかなるラベル付き文もその break
文を取り囲んでいなければ,コンパイル時エラーが発生する。
したがって,break
文は,常に中途完了するとみなすことができる。
前述では,単に"制御を移す"のではなく"制御を移そうとする"とした。これは,try
ブロックが break
文を含む try
文(14.19)が break
ターゲット内に存在すれば,それらの try
文のすべての finally
節は,制御がその break
ターゲットに移される前に,内側から外側に向かって順に実行されることによる。finally
節の中途完了は,break
文によって開始された制御の移動を中止することがある。
次の例では,数学的なグラフが配列の配列によって表されている。グラフは頂点の集合と辺の集合からなる。各辺は,ある頂点から別の頂点又は頂点からその頂点自体を指す矢とする。この例では,冗長な辺はないものと仮定する。すなわち,どの二つの頂点 P 及び Q (P 及び Q は同じこともある)に対しても,多くとも一つの P から Q への辺しか存在しない。頂点は整数で表され,すべての i 及び j に対して,頂点 i から頂点 edges[
i][
j]
への辺が存在し,それに対する配列の参照 edges[
i][
j]
は IndexOutOfBoundsException
を発生しない。
メソッド 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); } }
edgelist
及び search
,並びに break
文の使用に注意すること。これは,二つの別々の検査,頂点 i から頂点 j への辺に対する検査及び頂点 j から頂点 i への辺に対する検査の間で共有されるリストを一つの辺を除いてコピーする,というコードを可能にする。continue
文continue
文は,while
,do
又は for
文内にだけ出現してよい。これら3種類の文を,繰返し文(iteration statements)と呼ぶ。制御は,繰返し文のループ継続箇所に渡る。
ContinueStatement: continue Identifieropt ;ラベルなし
continue
文は,直接取り囲んでいるメソッド又は初期化子ブロック内の,取り囲んでいる最も内側の while
,do
又は for
文に制御を移そうとする。この文は,continue ターゲット(continue target)と呼ばれ,直ちに現在の繰返し処理を終了し,新たな繰返し処理を開始する。
正確には,この continue
文は,ラベルなし continue
という理由で常に中途完了する。直接取り囲んでいるメソッド又は初期化子ブロック内の,いかなる while
,do
又は for
文もその continue
文を取り囲まなければ,コンパイル時エラーが発生する。
ラベル Identifier 付き continue
文は,それを取り囲んでいる,同じ Identifier をラベルとする,ラベル付き文(14.7)に制御を移そうと試みる。その文は,continue ターゲット(continue target)と呼ばれ,直ちに現在の繰返し処理を終了し,新たな繰返し処理を開始する。continue ターゲットは,while
,do
又は for
文でなければならない。そうでなければ,コンパイル時エラーが発生する。continue 文は直接取り囲んでいるメソッド又は初期化子ブロック内のラベルを参照しなければならない。局所的でない制御の移動はない。
より正確には,ラベル Identifier 付きの continue
文は,常に,ラベル Identifier 付きの continue
という理由によって中途完了する。Identifier をそのラベルとしてもついかなるラベル付き文もそのcontinue
文を含まなければ,コンパイル時エラーが発生する。
したがって,continue
文は,常に中途完了するとみなすことができる。
continue
文による中途完了の処理に関しては,while
文(14.11),do
文(14.12)及び for
文(14.13)を参照のこと。
前述では,単に"制御を移す"のではなく"制御を移そうとする"とした。これは,continue ターゲット内に,try
ブロックが continue
文を含む try
文(14.19)が存在すれば,それらの try
文のすべての finally
節は,制御がその continue ターゲットに移される前に,内側から外側に向かって順に実行されることによる。finally
節の中途完了は,continue
文によって開始された制御の移動を中止することもある。
14.14 の Graph
の例では,break
文の一つが,一番外側の for
ループの本体全体の実行を終えるために使用された。この break
は,その for
ループ自体がラベル付けされていれば,continue
で置き換えることができる。
この例及び前述の例のどちらでもよいときに,どちらを使うかは,プログラムの書き方の問題となる。class Graph { . . . 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) { . . . } else if (k == j) { . . . } newedges[k] = edges[k]; continue edgelists; } // search . . . } // edgelists return new Graph(newedges); } }
return
文return
文は,メソッド(8.4,15.12)又はコンストラクタ(8.8,15.9)の呼出し元へ制御を返す。
ReturnStatement: return Expressionopt ;Expression なしの
return
文は,void
キーワードを使っていかなる値も返さないと(8.4)宣言されたメソッドの本体内,又はコンストラクタ(8.8)の本体内に含まれなければならない。return
文が静的初期化子(8.7)内に出現したら,コンパイル時エラーが発生する。Expression なしの return
文は,それを含むメソッド又はコンストラクタの呼出し元に制御を移そうとする。
正確には,Expression なしの return
文は,常に値なしの return
という理由で中途完了する。
Expression 付きの return
文は,値を返すと宣言されたメソッド(8.4)宣言内に含まれていなければならない。そうでなければ,コンパイル時エラーが発生する。Expression は,変数又はある型 T の値を表さなければならない。そうでなければ,コンパイル時エラーが発生する。型 T は,そのメソッドの,宣言された結果の型に代入可能(5.2)でなければならない。そうでなければ,コンパイル時エラーが発生する。
Expression 付きの return
文は,それを含むメソッドの呼出し元へ制御を移そうとする。Expression の値は,メソッド呼出しの値となる。より正確には,その return
文の実行は,最初にその Expression を評価する。Expression の評価が何らかの理由によって中途完了すれば,その return
文は同じ理由で中途完了する。Expression の評価が値 V を返して正常完了すれば,その return
文は,値Vを返す return
という理由で中途完了する。式が float
型であり,FP厳密 (15.4) ではないならば,その値は単精度数値集合又は単精度指数部拡張数値集合 (4.2.3) の要素である。式が double
型であり,FP厳密ではないならば,その値は倍精度数値集合又は倍精度指数部拡張数値集合の要素である。
したがって,return
文は,常に中途完了するとみなすことができる。
前述では,単に"制御を移す"のではなく"制御を移そうとする"とした。これは,メソッド又はコンストラクタ内に try
文(14.19)が存在し,try
ブロックが return
文を含むならば,それらの try
文のすべての finally
節は,制御がそのメソッド又はコンストラクタの呼出し元に移される前に,内側から外側に向かって順に実行されることによる。finally
節の中途完了が,return
文によって開始された制御の移動を中止することがある。
throw
文throw
文は,投げられることになる例外(11)を引き起こす。その結果は,投げられた値をとらえる try
文 (14.19) が見つかるまでの,複数の文及び複数のコンストラクタ,インスタンス初期化子,静的初期化及びフィールド初期化子の評価,及びメソッド呼出しを終了することもある制御のすみやかな移動 (11.3) である。try
文が見つからなければ,throw
を実行したスレッド(17.)の実行は,そのスレッドが属するスレッドグループに対するメソッド uncaughtException
の呼出しの後,終了する(11.3)。
ThrowStatement: throw Expression ;
throw
文内の Expression は,型 Throwable
に代入可能な(5.2)参照型の変数又は値を指定しなければならない。そうでなければ,コンパイル時エラーが発生する。さらに,次の3条件の内,少なくとも一つは真でなければならない。そうでなければ,コンパイル時エラーが発生する。
RuntimeException
又は RuntimeException
の下位クラスとなる。
Error
又は Error
の下位クラスとなる。
throw
文は,try
文(14.19)の try
ブロックに含まれ,Expression の型は,try
文の少なくとも一つの catch
節の仮引数の型に代入可能(5.2)とする。(この場合,投げられた値は try
文によって捉えられた(caught)という。)
throw
文は,メソッド又はコンストラクタ宣言に含まれ,Expression の型は,その宣言の throws
節(8.4.4,8.8.4)に列挙される型の少なくとも一つの型に代入可能(5.2)とする。
throw
文は,最初に,Expression を評価する。その Expression の評価が何らかの理由で中途完了すれば,throw
は,その理由によって中途完了する。Expression の評価が,非 null
の値 V を生成して正常完了すれば,throw
文は,値 V を伴う throw
という理由で中途完了する。Expression の評価が,null
値を生成して正常完了すれば,クラス NullPointerException
のインスタンス V' が生成され,null
の代わりに投げられる。そして throw
文は,値 V' を伴う throw
という理由で中途完了する。
したがって,throw
文は常に中途完了するとみなすことができる。
throw
文を try
ブロックに含むような取り囲む try
文(14.19)が存在すれば,それらの try
文のすべての finally
節は,制御が外に移るときに,投げられた値が捉えられるまで実行される。finally
節の中途完了は,throw
文によって開始された制御の移動を中止することもあることに注意すること。
throw
文が,あるメソッド宣言に含まれるが,その値がそれを含むある try
文によって捉えられなければ,そのメソッドの呼出しは,その throw
のために中途完了する。
throw
文が,あるコンストラクタ宣言に含まれるが,その値がそれを含むある try
文によって捉えられなければ,そのコンストラクタを呼び出す,クラスのインスタンス生成の式は,その throw
のために中途完了する。
throw
文が静的初期化子(8.7)に含まれれば,コンパイル時の検査が,その値が常に検査されない例外か,又はその値がそれを含む try
文によって常に捉えられるかのいずれかを保証する。この検査にもかかわらず,実行時に,その値がその throw
文を含む try
文に捉えられない場合,その値は,それがクラス Error
又はその下位クラスのインスタンスならば,再び投げられる。そうでなければ,オブジェクト ExceptionInInitializerError
に包まれて,投げられる(12.4.2)。
throw
文がインスタンス初期化子(8.6)に含まれれば,コンパイル時の検査が,その値が常に検査されない例外か,又はその値がそれを含む try 文によって常に捉えられるか,又は,投げられる例外(又はその上位クラス)の型がクラスのすべてのコンストラクタの throws 節に現れることを保証する。
慣例によって,ユーザ宣言された投げられることができる型は,通常,クラス Throwable
(11.5)の下位クラスであるクラスException
の下位クラスとして宣言される。
synchronized
文synchronized
文は,実行中のスレッドに代わって相互排他ロック(17.13)を獲得して,ブロックを実行し,ロックを解除する。実行中のスレッドがロックを所有している間,他のいかなるスレッドもロックを獲得できない。
SynchronizedStatement: synchronized ( Expression ) BlockExpression の型は,参照型でなければならない。そうでなければ,コンパイル時エラーが発生する。
synchronized
文は,最初に Expression を評価することによって実行される。
Expression の評価が,何らかの理由によって中途完了すれば,synchronized
文は同じ理由によって中途完了する。
そうでないときには,Expression の値が null
ならば,NullPointerException
が投げられる。
そうでなければ,Expression の非 null
の値を V とする。実行中のスレッドは,V に結びつけられるロックをかける。それから,Block が実行される。Block の実行が正常完了すれば,ロックは解除され,synchronized
文は正常完了する。Block の実行が中途完了すれば,ロックが解除され synchronized
文は同じ理由によって,中途完了する。
あるオブジェクトに結びついたロックの獲得は,それ自体,他のスレッドがそのオブジェクトのフィールドにアクセスしたり,非同期のメソッドをそのオブジェクトに対して起動したりするのを妨げはしない。他のスレッドも,相互排他を実現するために通常の方法で,synchronized
メソッド又は synchronized
文を使用することができる。
synchronized
文によって獲得されたロックは,synchronized
メソッドによって暗黙のうちに獲得されるロックと同じとする。8.4.3.6を参照のこと。一つのスレッドは,一つのロックを複数回保持できる。
この例は,次を出力する。class Test { public static void main(String[] args) { Test t = new Test(); synchronized(t) { synchronized(t) { System.out.println("made it!"); } } } }
単一スレッドが複数回ロックをかけられなければ,この例はデッドロックになる。made it!
try
文try
文は,ブロックを実行する。値が投げられ,try
文がそれを捕捉できる一つ以上の catch
節をもつとき,制御はそれらの最初の catch
節に移される。その try
文が finally
節をもてば,try
ブロックが正常完了するか中途完了するかに関わらず,さらには,どれかの catch
節に先に制御が渡されたかどうかに関わらず,finally
節のコードブロックが実行される。
TryStatement: try Block Catches try Block Catchesopt Finally Catches: CatchClause Catches CatchClause CatchClause: catch ( FormalParameter ) Block Finally: finally Blockここに示したことを明確にするために,次を再び8.4.1から示す。
FormalParameter: finalopt Type VariableDeclaratorIdここに示したことを明確にするために,次を再び8.3から示す。
VariableDeclaratorId: Identifier VariableDeclaratorId [ ]
try
キーワードのすぐ後の Block を,その try
文の try
ブロックと呼ぶ。finally
キーワードのすぐ後の Block を,その try
文の finally
ブロックと呼ぶ。
try
文は,catch
節 (例外ハンドラ(exception handlers) とも呼ぶ) をもってもよい。catch
節は,必ずちょうど1個の仮引数 (例外仮引数(exception parameter)と呼ぶ) をもたなければならない。例外仮引数の宣言された型はクラス Throwable
又は Throwable
の下位クラスでなければならない。そうでなければ,コンパイル時エラーが発生する。仮引数変数の有効範囲は,catch
節の Block とする。
catch 節の例外仮引数は,その catch 節を直接取り囲むメソッド又は初期化子ブロックの局所変数又は仮引数と同じ名前をもってはいけない。そうでなければ,コンパイル時エラーが発生する。
try
文(14.19)の catch
節で宣言された例外ハンドラの仮引数の有効範囲は,catch
と関連付けられたブロック全体とする。
catch
節の Block 内では,仮引数の名前は,直接取り囲むメソッド又は初期化子ブロックの局所変数として再宣言されてはならないし,直接取り囲むメソッド又は初期化子ブロックの try 文の catch 節の例外仮引数として再宣言されてもならない。再宣言されると,コンパイル時エラーが発生する。しかしながら,例外仮引数は catch 節の Block 内で入れ子となったクラス宣言の内部ではおおい隠される(6.3.1)。
final と宣言された例外仮引数が catch 節の本体で代入されたなら,コンパイル時エラーが発生する。
例外仮引数は,限定名 (6.6) で参照できない。単純名に限る。
例外ハンドラは,左から右の順に考慮される。最初の可能な catch
節が,投げられた例外オブジェクトを実引数として受け取って,例外を捕捉する。
finally
節は,try
ブロック又は catch
ブロックから制御がどのように離れたかに関わらず,try
ブロック及び実行されたかもしれないcatch
ブロックの後で,finally
ブロックが実行されることを保証する。
finally
ブロックの処理は,少し複雑となる。そのため,try
文を finally
ブロックをもつ場合及びもたない場合の二つに分けて示す。
finally
ブロックをもたない try
文は,最初に try
ブロックの実行によって実行される。そのとき,次の選択が存在する。
try
ブロックの実行が正常完了した場合,何の動作も起こらず,try
文は正常完了する。
try
ブロックの実行が値 V の throw
のために中途完了した場合,次の選択が存在する。
try
文の幾つかの catch
節の Parameter に代入可能(5.2)ならば,そのうち最初の (最も左の) catch
節が選ばれる。その値 V が選ばれた catch
節の仮引数に代入され,catch
節の Block が実行される。そのブロックが正常完了すれば,try
文は正常完了する。そのブロックがなんらかの理由によって中途完了すれば,try
文は同じ理由で中途完了する。
try
文のどの catch
節の仮引数にも代入可能ではないとき,その try
文は,値 V の throw
という理由によって中途完了する。
try
ブロックの実行が何らかの理由によって中途完了するとき,その try
文は同じ理由によって中途完了する。
例外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("RuntimeException:" + r); } catch (BlewIt b) { System.out.println("BlewIt"); } } }
BlewIt
は,メソッド blowUp
によって投げられる。main
本体の try
-catch
文は,二つの catch
節をもっている。例外の実行時の型が,型 RuntimeException
の変数に代入可能ではなく,型 BlewIt
の変数に代入可能な BlewIt
なので,この例の出力は,次のとおりとなる
BlewIt
finally
ブロックをもつ try
文は,最初に try
ブロックの実行によって実行される。そのとき,次の選択が存在する。
try
ブロックの実行が正常完了すれば,finally
ブロックが実行され,さらに次の選択が存在する。
try
ブロックが,値 V の throw
という理由によって中途完了すれば,次の選択が存在する。
try
文の幾つかの catch
節の仮引数に代入可能なとき,その最初の (最も左の) catch
節が選ばれる。値 V が,選ばれた catch
節の仮引数に代入され,その catch
節の Block が実行される。そのとき,次の選択が存在する。
catch
ブロックが正常完了すれば,finally
ブロックが実行される。そのとき,次の選択が存在する。
catch
ブロックが理由 R によって中途完了すれば,finally
ブロックが実行される。そのとき,次の選択が存在する。
try
文の幾つかの catch
節の仮引数に割当て不可能ならば,finally
ブロックが実行される。そのとき,次の選択が存在する。
try
ブロックの実行が他のいずれかの理由 R によって中途完了すれば,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("BlewIt"); } finally { System.out.println("Uncaught Exception"); } } }
メソッドUncaught Exception java.lang.NullPointerException at Test.blowUp(Test.java:7) at Test.main(Test.java:11)
blowUp
によって投げられる NullPointerException
(RuntimeException
の一種) は,main
の try
文には捕捉されない。これは,NullPointerException
は BlewIt
型の変数に割り当てられないことによる。これによって finally
節が実行され,その後,main
を実行しているスレッドであって,テストプログラムの唯一のスレッドが,捕捉されない例外のために終了し,通常は例外名及び簡単な処理の軌跡を印字する。
14.20では,"到達可能"という言葉の厳密な規定を行う。それは,文を含むコンストラクタ,メソッド,インスタンス初期化子又は静的初期化子の初めから,その文自体に至る何らかの実行経路が必ず存在しなければならないこととする。解析は,文の構造を考慮に入れる。条件式が定数値 true
をもつときの,while
,do
及び for
文の特別な扱いを除き,式の値はフロー解析では考慮に入れない。
コンパイラの受理は,{ int n = 5; while (n > 7) k = 2; }
n
の値がコンパイル時に分かっており,k
への代入が決して実行されないことを理論的に知ることができるとしても行われる。Javaコンパイラは,14.20で述べる規則にしたがって動作しなければならない。
ここでの定義は,文が到達可能なときに限り,その文が正常完了することを可能とする。
if
文は,それが else
部をもつかどうかに関わらず,通常とは違う方法で扱う。このため,これは14.20の最後に別に示す。
switch
文は,次の少なくとも一つが真のときに限り正常完了可能とする。
default
ラベルを含まない。
switch
文を抜け出す,到達可能な break
文が存在する。
switch
文が到達可能なときに限り,到達可能とする。
switch
文が到達可能であって,さらに次の少なくとも一つが真のときに限り到達可能とする。
while
文は,次の少なくとも一つが真のときに限り,正常完了可能とする。
while
文が到達可能であって,条件式が値 false
の定数式でないときに限り,到達可能とする。
do
文は,次の少なくとも一つが真のときに限り,正常完了可能とする。
do
文が到達可能なときに限り到達可能とする。
for
文は,次の少なくとも一つが真のときに限り,正常完了可能とする。
for
文が到達可能であって,条件式が値 false
の定数式でないときに限り,到達可能とする。
break
,continue
,return
又は throw
文は正常完了可能ではない。
synchronized
文は,含まれる文が正常完了可能なときに限り,正常完了可能とする。含まれる文は,synchronized
文が到達可能なときに限り,到達可能とする。
try
文は,次の二つがともに真のときに限り,正常完了可能とする。
try
ブロックは,try
文が到達可能なときに限り到達可能とする。
catch
ブロック C は,次の二つがともに真のときに限り,到達可能とする。
try
ブロック内の幾つかの式又は throw
文が到達可能であって,型が catch
節 C の仮引数に代入可能な例外を投げる。(式は,それを含む最も内側の文が到達可能なときに限り到達可能とみなされる。)
try
文内で,より前に存在する catch
ブロック A であって,C の仮引数の型が A の仮引数の型と同じ又は下位クラスであるものが存在しない。
finally
ブロックがあれば,それは try
文が到達可能なときに限り到達可能とする。
if
文が,次の方法で扱われることは期待してもよいが,これらはJavaプログラム言語が実際に使っている規則ではない。if-then
文は,次の少なくとも一つが true
のときに限り,正常完了可能とする。
then
文は,if
-then
文が到達可能であって,条件式の値が false
の定数式ではないときに限り,到達可能とする。
if
-then
-else
文は,then
文が正常完了可能か,又は else
文が正常完了可能なときに限り,正常完了可能とする。then
文は,if
-then
-else
文が到達可能であって,その条件式の値が false
の定数式ではないときに限り,到達可能とする。else
文は,if
-then
-else
文が到達可能であって,その条件式の値が true
の定数式ではないときに限り,到達可能とする。
この方法は,他の制御構造の扱いと一貫性がある。しかし,if 文が,"条件コンパイル"の目的で便利に使用可能とするために,実際の規則は異なっている。
if
-then
文は,それが到達可能なときに限り,正常完了可能とする。then
文は,if
-then
文が到達可能なときに限り,到達可能とする。
if
-then
-else
文は,then
文が正常完了可能か,又は else
文が正常完了可能なときに限り,正常完了可能とする。then
文は,if
-then
-else
文が到達可能なときに限り,到達可能とする。else
文は,if
-then
-else
文が到達可能なときに限り,到達可能とする。
これは,while (false) { x=3; }
x=3;
という文が,到達可能ではないことによる。しかし,見かけ上は同じ,次の場合はコンパイル時エラーとはならない。
最適化コンパイラは,文if (false) { x=3; }
x=3;
は決して実行されないことに気付き,その文に対するコードを生成したクラスファイルから除くことを選択するかも知れない。しかし,文 x=3;
は,ここで示された技術的な意味では"到達不能"とはみなされない。この異なる扱いに対する理由によって,プログラマは,次の"フラグ変数"の定義をしてよい。
さらに,次のコードを書く。static final boolean DEBUG = false;
これは,if (DEBUG) { x=3; }
DEBUG
の値を false
から true
へと,true
から false
へと変えることで,プログラム文に他の変更は加えずに正しくコードをコンパイル可能とするということにある。
この"条件コンパイル"が可能なことは,バイナリ互換性(13)に対して,大きな影響及び関係をもつ。"フラグ"変数を使うクラスの集合がコンパイルされ,条件コードが省かれれば,後で,そのフラグの定義を含むクラス又はインタフェースの新しい版だけを配布するのでは不十分となる。フラグの値の変更は,それ故に,既存のバイナリとバイナリ互換ではない(13.4.8)。(それらの非互換性には,switch
文内の case
ラベルにおける定数の使用などの他の理由も存在する。13.4.8を参照のこと。)
目次 | 前 | 次 | 索引 | Java言語規定 第2版 |