Javaプログラムの実行系列は,文(Statement) によって制御される。文はそれらの効果のために実行され,値をもたない。
構造の一部として他の文を 含む(contain) 文もある。それらの他の文を副文とする。文 S が文 T を含み,文 T が文 U を含み,文 S 及び文 U と異なる,文 T が存在しない場合に,文 S が文 U を直接含む(immediately contains)という。同じ方法で,その構造の一部として式を含む文もある (15.)。
14.1は,文の正常完了と中途完了との区別を示す。それ以降では,様々な種類の文についてそれらの正常な振舞い及び中途完了に対する特別な処置を詳細に示す。
ブロックは,他の種類の文が許されない場所に出現し,局所変数宣言文(14.3)という特定の文がブロックに直接含まれなければならないので,最初にブロックを示す(14.2)。
次に,熟知された“ぶら下がりelse”問題(14.4)を回避する文法処理について示す。
C及びC++ プログラマに身近な文としては,空文(14.5),ラベル付き文(14.6),式文(14.7),if
文(14.8),switch
文(14.9),while
文(14.10),do
文(14.11),for
文(14.12),break
文(14.13),continue
文(14.14),及びreturn
文(14.15)がある。
C及びC++と異なり,Javaは goto
文をもたない。しかし,break
文及び continue
文は,Javaでは拡張され,文のラベルに言及できる。
C言語にない Java
文には,throw
文(14.16),synchronized
文(14.17) 及び try
文(14.18)がある。
14.19 は,すべての文が技術的な意味で 到達可能(reachable) でなければならないという要求を示す。
すべての文は,計算ステップを実行する正常実行モードをもつ。次の節は,各種類の文の正常実行モードを示す。以降では,各種類の文の正常実行モードを示す。すべてのステップが中途完了の表示なしに実行されれば,文は,正常完了したという。しかし,イベントが文の正常完了を妨げることもある。
break
文(14.13),continue
文(14.14),return
文(14.15)は,制御転送を引き起こし,それらを含む文の正常完了を妨げることがある。
throw
文(14.16)も,例外を発生する。例外は,制御転送を引き起こし,文の正常完了を妨げることがある。
これらのイベントが発生した場合には,正常実行モードでの全ステップが完了する前に,一つ以上の文の実行を完了してもよい。それらの文は中途完了したという。中途完了は,常に(次の内の一つの)関連理由(reason) をもつ。
break
break
continue
continue
return
return
throw
も含めた,値付きの throw
“正常完了”及び“中途完了”という用語は,式(15.5)の評価においても同じく適用される。式が中途完了する理由は,値付きの throw
文(14.16),実行時の例外又はエラー(11, 15.5)
のいずれかによる例外の発生に限られる。
文中で式が評価される場合,式の中途完了は常に即座に文の中途完了を同じ理由で引き起こす。正常実行モードにおける引き続いた実行ステップのすべては実行されない。
14.においては,特に指定しない限り,副文の中途完了は,文そのものの中途完了を即座に同じ理由に関して引き起こし,その文の正常実行モードにおける残りの実行ステップのすべては実行されない。
別途指定しない限り,すべての式の評価及びすべての副文の実行が正常完了すると,文も正常完了する。
ブロック(Block) は,中括弧で括られた一連の文及び局所変数宣言文とからなる。
Block:{
BlockStatementsopt}
BlockStatements:BlockStatement
BlockStatements
BlockStatement BlockStatement: LocalVariableDeclarationStatement Statement
ブロックの実行では,各局所変数宣言文及びその他の文は,順番に初めから終わりまで(左から右へ)実行される。これらすべてのブロック内の文が正常完了すると,ブロックも正常完了する。これらのうちの文のどれかが,何らかの理由で中途完了すると,同じ理由でブロックも中途完了する。
局所変数宣言文(local variable declaration statement) は,一つ以上の局所変数名を宣言する。
LocalVariableDeclarationStatement: LocalVariableDeclaration;
LocalVariableDeclaration: TypeVariableDeclarators
ここでの記述を更に明瞭にするために,8.3 での記述をここで繰り返す。
VariableDeclarators: VariableDeclarator VariableDeclarators,
VariableDeclarator VariableDeclarator: VariableDeclaratorId VariableDeclaratorId=
VariableInitializer VariableDeclaratorId: Identifier VariableDeclaratorId[ ]
VariableInitializer: Expression ArrayInitializer
すべての局所変数宣言文は,ブロックに直接含まれる。局所変数宣言文はブロックにおける他の種類の文と自由に混在してよい。
局所変数宣言は,for
文の先頭に置かれることもある(14.12)。この場合,それが局所変数宣言文の一部であったかのように実行される。
局所変数宣言における各 宣言子(Declarator) は,一つの局所変数を宣言する。宣言子に現れる 識別子(Identifier) が名前となる。
変数の型は,局所変数宣言の冒頭に現れる 型指定子(Type) によって指定される。型指定子の後には宣言子内の 識別子(Identifier) が存在し,さらに角括弧対が続く。したがって,次行の局所変数宣言は次次行以降の一連の宣言に相当する。
int a, b[], c[][];
int a; int[] b; int[][] c;
C及びC++の伝統を尊重し,角括弧が宣言子内で利用できる。しかし,一般規則としては,次行の局所変数宣言が,次次行以降の一連の宣言に相当する。
float[][] f[][], g[][][], h[]; // Yechh!
float[][][][] f; float[][][][][] g; float[][][] h;
ブロック内で宣言された局所変数の有効範囲は,ブロックの残り部分とし,変数そのものの初期化子を含む。局所変数仮引数の名前は,その有効範囲内の局所変数又は例外仮引数として再宣言してはならない。再宣言すると,コンパイル時エラーとなる。すなわち,局所変数の名前を隠すことは許されない。
局所変数は,限定名(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); } }
この制限は,その他の非常に不明瞭なバグを検出するのに役立つ。 (局所変数によるメンバの隠ぺいに関する同様の制限は,スーパクラスにおけるメンバの追加によってサブクラスの局所変数の名前を改めなければならないので,実用的でないと判断した。)
一方,同じ名前の局所変数を,互いに他を含まない二つの別々のブロック又は 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
局所変数として宣言された名前が,フィールド又は型名として既に宣言されていれば,その外のフィールド又は型名としての宣言は,局所変数の有効範囲では隠ぺいされる。それらのフィールド又は型名も,適当な限定付きの名前を用いることによって,ほとんど常に(6.8)アクセス可能とする。例えば,キーワード
this
を用い,形式 this.x
によって,隠ぺいされたフィールド x
にアクセスできる。実際,次の形式がコンストラクタ(8.6)に典型的に出現する。
class Pair { Object first, second; public Pair(Object first, Object second) { this.first = first; this.second = second; } }
この例では,コンストラクタは,初期化されるフィールドと同じ名前の仮引数をもつ。これは,仮引数ごとに異なる名前を生成しなければならない場合よりは簡単であって,この様式化された文脈においては,さほど混乱を生じない。しかし,一般には,フィールドと同じ名前を局所変数に割り当てることは,よい書式ではない。
局所変数宣言文は,実行可能とする。実行のたびに,宣言子は,左から右の順番に処理される。宣言子が初期化式をもっていれば,その式は評価され,その値が変数に割り当てられる。宣言子が初期化式をもっていない場合には,Javaコンパイラは16で与えられたアルゴリズムを使って,すべての変数参照について,値の割当ての実行が先行していることを検証しなければならない。検証に失敗したら,コンパイル時エラーが発生する。
第1番目を除いて,各初期化は,その前の初期化式の評価が正常完了した場合にだけ,実行される。局所変数宣言の実行は,最後の初期化式の評価が正常完了して初めて,正常完了する。局所変数宣言が初期化式を全く含まなければ,常にその実行は正常完了する。
Java言語には多くの種類の文がある。普通はC及びC++言語における文に対応するが,Javaに特有なものもある。
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 StatementNoShortIf: StatementWithoutTrailingSubstatement LabeledStatementNoShortIf IfThenElseStatementNoShortIf WhileStatementNoShortIf ForStatementNoShortIf StatementWithoutTrailingSubstatement: Block EmptyStatement ExpressionStatement SwitchStatement DoStatement BreakStatement ContinueStatement ReturnStatement SynchronizedStatement ThrowStatement TryStatement
次に,ここでの記述を更に明瞭にするために,14.8を再掲する。
IfThenStatement:if (
Expression)
Statement IfThenElseStatement:if (
Expression)
StatementNoShortIfelse
Statement IfThenElseStatementNoShortIf:if (
Expression)
StatementNoShortIfelse
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.13)又は continue
文(14.14)によって使われる。
識別子によってラベル付けされた文は,同じ識別子によってラベル付けされた他の文内には現れてはならない。現れた場合は,コンパイル時エラーが発生する。文がお互いに包含関係になければ,2文が同じ識別子でラベル付けされてもよい。
同じ識別子をラベル,パッケージ,クラス,インタフェース,メソッド,フィールド,仮引数又は局所変数の名前として使うことに対する制限はない。文にラベル付するために使用した識別子は,パッケージ,クラス,インタフェース,メソッド,フィールド,仮引数及び局所変数が同じ名前を用いても隠ぺいしない。局所変数や例外ハンドラ(14.18) の仮引数としての同じ名前の識別子が使用されても,それは文のラベルを隠ぺいしない。
ラベル付き文は,直接含まれた Statement の実行によって実行される。文が Identifier によってラベル付けされ,その含む Statement が同じ Identifier をもつ break
文によって中途完了すれば,ラベル付き文は正常完了する。Statement が他の理由で中途完了した場合には,ラベル付き文も同じ理由で中途完了する。
ある種の式は,その後にセミコロンが続くことによって文として使われる。
ExpressionStatement:
StatementExpression ;
StatementExpression:
Assignment
PreIncrementExpression
PreDecrementExpression
PostIncrementExpression
PostDecrementExpression
MethodInvocation
ClassInstanceCreationExpression
式文は,式を評価することによって実行される。式が値をもっても,その値は放棄される。
式文の実行が正常完了するのは,式の評価が正常完了するのと等価とする。
C及びC++ と異なり,Java言語では,限られた形の式だけが式文として使われる。Javaでは void
という型はないので,“出力を型 void
にキャストする”という伝統的なCでの,次の式文の書き方はJavaでは許されないことに注意する必要がある。
(void) ... ; // This idiom belongs to C, not to Java!
一方,Javaは,式文においてすべての最も有益な種類の式を許す。またJavaでは,型 void
のメソッドを呼び出すために,式文としてメソッド呼出しを使う必要がない。したがって,前述のトリックは,ほとんど必要ない。このトリックが必要なら,値割当て文(15.25) 又は局所変数宣言文(14.3)のいずれかを,その代りに使用できる。
if
文
If
文は,文の条件付きの実行,又は2文の条件付き選択の内,両方ではなく,どちらか一方だけの実行の選択を可能とする。
IfThenStatement:if (
Expression)
Statement IfThenElseStatement:if (
Expression)
StatementNoShortIfelse
Statement IfThenElseStatementNoShortIf:if (
Expression)
StatementNoShortIfelse
StatementNoShortIf
式(Expression) は,論理型をもたなければならない。そうでなければ,コンパイル時エラーが発生する。
if-then
文
if-then
文の実行では,最初に 式(Expression) を評価する。式の評価が何らかの理由で中途完了すると,if-then
文も同じ理由で中途完了する。そうでなければ,式の評価の結果生じる値に基づいて選択が行われる。
if-then
文の実行は,含まれる文の実行が正常完了したときに限り,正常完了する。 if-then
文は正常完了する。 if-then-else
文
if-then-else
文は,最初に 式(Expression) を評価することによって実行される。Expression の評価が何らかの理由で中途完了すれば,if-then-else
文も同じ理由で中途完了する。そうでなければ,式の評価の結果生じる値に基づいて,選択が行われる。
else
の前にある文)が実行される。その文が正常完了する限り,if-then-else
文は正常完了する。 else
の後ろの文)が実行される。その文が正常完了する限り,if-then-else
文は正常完了する。 switch
文switch
文は,式の値に応じていくつかの文のいづれかへ制御を移す。
SwitchStatement:switch (
Expression)
SwitchBlock SwitchBlock:{
SwitchBlockStatementGroupsoptSwitchLabelsopt
}
SwitchBlockStatementGroups:SwitchBlockStatementGroup
SwitchBlockStatementGroups
SwitchBlockStatementGroup SwitchBlockStatementGroup: SwitchLabels
BlockStatements SwitchLabels: SwitchLabel SwitchLabels
SwitchLabel SwitchLabel:
case
ConstantExpression: default :
式(Expression)の型は,char
,byte
,short
又は int
のいづれかでなければならない。そうでなければ,コンパイル時エラーが発生する。
switch
文の本体は,ブロックでなければならない。ブロックに直接含まれる文は,一つ以上のcase
ラベル又はdefault
ラベルによってラベル付けされる。case
ラベルにおける定数式(15.27)の値と共に,これらのラベルは,switch
文に関連しているという。
次のすべては,真でなければならない。そうでなければ,コンパイル時エラーが生じる。
switch
文に関連したすべてのcase
の定数式は,switch
式の型に割当て可能でなければならない(5.2) 。 switch
文に関連したcase
の定数式は,どの二つも同じ値をもってはならない。
switch
文に関連可能なdefault
ラベルは,一つだけとする。
C及びC++においては,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 in Java. case 6: foo(); case 5: foo(); case 4: foo(); case 3: foo(); case 2: foo(); case 1: foo(); } while (--q >= 0); }
幸いにも,このトリックは,広く知られてもいないし,使われてもいないと思われる。しかも,今日ではあまり必要としない。この種のコード変換は,最新式の最適化コンパイラの適切な処理として含まれている。
switch
文が実行されるときには,最初に,式が評価される。式の評価が何らかの理由で中途完了すると,switch
文も同じ理由で中途完了する。そうでなければ,各case
の定数と式の値との比較を実行する。それから,選択が行われる。
case
定数の内のどれか一つが式の値と等しい場合,case
が合致したといい,switch
ブロックの合致したcase
ラベルの後のすべての文が,もし存在すれば,順に実行される。これらの文がすべて正常完了するか,又は合致したcase
ラベルの後に文が存在しなければ,switch
文全体が正常完了する。
case
も合致しないが,default
ラベルが存在するときには,switch
ブロックにおけるdefault
ラベルの後のすべての文が,存在すれば,順に実行される。これらの文がすべて正常完了するか,default
ラベルの後に文がなければ,switch
文の全体が正常完了する。
case
が合致せず,default
ラベルのない場合,さらなる何も動作が起こらず,switch
文は正常完了する。
switch
文のブロック本体に直接含まれる文が中途完了した場合には,次のとおりに扱われる。
break
によって 文(Statement) の実行が中途完了した場合には,さらなる動作は起こらず,switch
文は正常完了する。
switch
文も同じ理由で中途完了する。ラベル付き
break
による中途完了の場合は,ラベル付き文 (14.6) のための一般規則にしたがって扱われる。
C及びC++ と同様Java においても,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
のコードにつながっていく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 } } public static void main(String[] args) { howMany(1); howMany(2); howMany(3); } }
one two many
while
文
文では,式(Expression) の値が偽になるまで,式(Expression) 及び 文(Statement) を繰り返し実行する。
WhileStatement:while (
Expression)
Statement WhileStatementNoShortIf:while (
Expression)
StatementNoShortIf
Expression は論理型でなければならない。そうでなければ,コンパイル時エラーが発生する。
while
文の実行では,最初に Expression が評価される。Expression の評価が何らかの理由で中途完了すれば,while
文も同じ理由で中途完了する。そうでなければ,式の値に基づいて次の選択をする。
while
文全体が再実行される。 while
文は正常完了する。 初めて評価されるときに,式の値が偽ならば,文は実行されない。
break
によって文の実行が中途完了した場合には,さらなる動作は起こらず
while
文は正常完了する。 continue
によって文の実行が中途完了した場合には,while
文全体が再び実行される。 continue
文によって文の実行が中途完了した場合には,次の選択がある。 while
文も同じ理由で中途完了する。ラベル付き break
による中途完了の場合は,ラベル付き文 (14.6)
の一般法則によって扱われることに注意せよ。 do
文
文では 式(Expression) の値が偽になるまで,式(Expression) 及び 文(Statement) を繰り返し実行する。
DoStatement:do
Statementwhile (
Expression) ;
Expression は論理型でなければならない。そうでなければ,コンパイル時エラーが発生する。
do
文の実行では,最初に Statement が実行される。そのとき,次の選択が存在する。
do
文も同じ理由で中途完了する。そうでなければ,式の値に基づく次の選択がある。
do
文の実行では,含まれる文は常に少なくとも 1
度は実行される。
break
で中途完了した場合には,さらなる動作は起こらず,do
文は正常完了する。 continue
で中途完了した場合には,式が評価される。その結果生じる値に基づいて選択を行う。
continue
のために中途完了すれば,次の選択がある。
do
文は,同じ理由で中途完了する。ラベル付き break
による中途完了の場合は,一般法則 (14.6) によって扱われる。 do
文の例
次のコードは,クラス Integer
のメソッド toHexString
(20.7.14)の一つの実装例とする。
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
文は,初期化コードを実行し,式(Expression)の値がfalse
となるまで,Expression,文(Statement) 及び更新のコードを繰返し実行する。
ForStatement:for (
ForInitopt;
Expressionopt;
ForUpdateopt)
Statement ForStatementNoShortIf:for (
ForInitopt;
Expressionopt;
ForUpdateopt)
StatementNoShortIf ForInit: StatementExpressionList LocalVariableDeclaration ForUpdate: StatementExpressionList StatementExpressionList: StatementExpression StatementExpressionList,
StatementExpression
Expression は,boolean
型でなければならない。そうでなければ,コンパイル時エラーが発生する。
for
文の実行は,最初に ForInit コードの実行によって行なわれる。
for
文も同じ理由で中途完了する。中途完了した文の右側のいずれの ForInit 文も評価されない。 for
文の Expression,ForUpdate及び含まれている Statement とする。局所変数宣言が何らかの理由によって中途完了した場合,for
文は同じ理由で中途完了する。
for
文は同じ理由で中途完了する。そうでなければ,Expression が存在するかどうか,及び Expression が存在すれば,その結果の値にもとづいた選択が存在する。
for
文は同じ理由によって中途完了する。中途完了した文の右側のいずれの ForUpdate
文も評価されない。ForUpdate 部がなければ,何の動作も起こらない。
for
繰返し処理が実行される。
false
ならば,以降の処理は行なわれず,for
文は正常完了する。
Expression の値が最初の評価でfalse
であれば,Statement は実行されない。
Expression が存在しなければ,break
文を使用したときだけ,for
文は正常完了可能とする。
break
文のために中途完了すれば,それ以降の処理は行なわれず,for
文は正常完了する。
continue
文のために中途完了すれば,次の二つの処理が順に実行される。
for
文の繰返しが行なわれる。
continue
文のために中途完了すれば,次の選択が存在する。
for
文がラベルLをもてば,次の二つの手順が順に行なわれる。
for
文の繰返しが行なわれる。
for
文がラベルLをもたなければ,そのfor
文はラベルL付きの continue
文のために中途完了する。 for
文は同じ理由で中途完了する。ラベル付きのbreak
文による中途完了の場合は,ラベル付き文(14.6)の一般規則によって扱われることに注意すること。
break
文BreakStatement:break
Identifieropt;
ラベル無しのbreak
文は囲まれている最も内側のswitch
,while
,do
又はfor
文に制御を移す。この文はbreakターゲット(break target)と呼ばれ,直ちに正常完了する。正確には,ラベル無しbreak
文は,いつもラベル無しbreak
文という理由で中途完了する。いかなるswitch
,while
,do
又はfor
文も,break
文を囲っていなければ,コンパイル時エラーが発生する。
ラベル 識別子(Identifier)
付きのbreak
文は,その Identifier
と同じラベルをもち,それを囲っているラベル付き文(14.6)に制御を移そうとする。この文はbreakターゲット(break target)と呼ばれ,直ちに正常完了する。この場合,break
ターゲットは,while
,do
,for
又はswitch
文でなくてもよい。正確には,ラベル識別子付きbreak
文は,常にラベル識別子付きbreak
文という理由で中途完了する。ラベルとしての Identifier をもった,いかなるラベル付き文もそのbreak
文を囲っていなければ,コンパイル時エラーが発生する。
従って,break
文は,常に中途完了するとみなすことができる。
前述では,単に“制御を移す”のではなく“制御を移そうとする”とした。これは,try
ブロックがbreak
文を含むbreak
ターゲット内に,try
文(14.18)が存在すれば,それらのtry
文のすべてのfinally
節は,制御がそのbreak
ターゲットに移される前に,内側から外側に向かって順に実行されることによる。finally
節の中途完了は,文によって開始された制御の移動を中止することがある。
次の例では,数学的なグラフが配列の配列によって表されている。グラフは節の集合と枝の集合からなる。各枝は,ある節から別の節又は節からその節自体を指す矢とする。この例では,冗長な枝はないものと仮定する。すなわち,どの二つの節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
文
文は,while
,do
又はfor
文内にだけ出現してよい。これら3種類の文を,
繰返し文(iteration statements)と呼ぶ。制御は,繰返し文のループ継続箇所に渡る。
ContinueStatement:continue
Identifieropt;
ラベル無しcontinue
文は,それを囲む最も内側のwhile
,do
又はfor
文に制御を移そうとする。この文は,continueターゲット(continue target)と呼ばれ,直ちに現在の繰返し処理を終了し,新たな繰返し処理を開始する。正確には,このcontinue
文は,ラベル無しcontinue
文という理由で常に中途完了する。いかなるwhile
,do
又はfor
文がそのcontinue
文を囲まなければ,コンパイル時エラーが発生する。
ラベル識別子付きcontinue
文は,同じ 識別子(Identifier) をそのラベルとしてもち,それを囲むラベル付き文(14.6)に制御を移そうと試みる。その文は,continueターゲット(continue target)と呼ばれ,現在の繰返し処理を終了し,新たな繰返し処理を開始する。continue ターゲットは,while
,do
又はfor
文でなければならない。そうでなければ,コンパイル時エラーが発生する。より正確には,ラベル識別子付きのcontinue
文は,常に,ラベル識別子付きのcontinue
文という理由によって中途完了する。識別子(Identifier) をそのラベルとしてもついかなるラベル付き文もそのcontinue
文を含まなければ,コンパイル時エラーが発生する。
したがって,continue
文は,常に中途完了するとみなすことができる。
文による中途完了の処理に関しては,while
文(14.10),do
文(14.11)及びfor
文(14.12)を参照のこと。
前述では,単に“制御を移す”のではなく“制御を移そうとする”とした。これは,continueターゲット内に,try
ブロックがcontinue
文を含むtry
文(14.18)が存在すれば,それらのtry
文のすべてのfinally
節は,制御がそのcontinueターゲットに移される前に,内側から外側に向かって順に実行されることによる。finally
節の中途完了は,continue
文によって開始された制御の移動を中止することもある
前節のグラフ
の例では,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.11)又はコンストラクタ(8.6,15.8)の呼出し元へ制御を返す。
ReturnStatement:return
Expressionopt;
Expression 無しのreturn
文は,void
キーワードを使っていかなる値も返さなく(8.4)宣言されたメソッドの本体内,又はコンストラクタ(8.6)の本体内に含まれなければならない。return
文が静的初期化子(8.5)内に出現したら,コンパイル時エラーが発生する。Expression 無しのreturn
文は,それを含むメソッド又はコンストラクタの呼出し元に制御を移そうとする。正確には,Expression 無しのreturn
文は,常に値なしのreturn
文という理由で処理を中途完了する。
Expression 付きのreturn
文は,値を返すと宣言されたメソッド(8.4)宣言内に含まれていなければならない。sぴでなければ,コンパイル時エラーが発生する。Expression は,変数又はある型Tの値を表さなければならない。そうでなければ,コンパイル時エラーが発生する。型Tは,そのメソッドの,宣言された結果の型に代入可能(5.2)でなければならない。そうでなければ,コンパイル時エラーが発生する。
Expression 付きのreturn
文は,それを含むメソッドの呼出し元へ制御を移そうとする。Expression の値は,メソッド呼出しの値となる。より正確には,そのreturn
文の実行は,最初にその Expression を評価する。Expression の評価が何らかの理由によって中途完了すれば,そのreturn
文は同じ理由で中途完了する。Expression の評価が値Vを返して正常完了すれば,そのreturn
文は,値Vを返すreturn
文という理由で中途完了する。
したがって,return
文は,常に中途完了するとみなすことができる。
前述では,単に“制御を移す”のではなく“制御を移そうとする”とした。これは,メソッド又はコンストラクタ内に,try
ブロックがreturn
文を含むtry
文(14.18)が存在すれば,それらのtry
文のすべてのfinally
節は,制御がそのメソッド又はコンストラクタの呼出し元に移される前に,内側から外側に向かって順に実行されることによる。finally
の中途完了が,return
文によって開始された制御の移動を中止することがある。
throw
文throw
文は,投げられることになる例外(11)を引き起こす。その結果は,複数の文及び複数のコンストラクタを終了することもある制御(11.3)のすみやかな移動,静的及びフィールド初期化子の評価,並びに投げられた値をとらえるtry
文(14.18)が見つかるまでのメソッド呼出し,とする。try
文が見つからなければ,throw
を実行したスレッド(17,20.20)の実行は,そのスレッドが属するスレッドグループに対するメソッドUncaughtException
(20.21.31)の呼出しの後,終了する(11.3)。
ThrowStatement:throw
Expression;
throw
文内の Expression は,型Throwable
に代入可能な(5.2)参照型の変数又は値を指定しなければならない。そうでなければ,コンパイル時エラーが発生する。さらに,次の3条件の内,少なくとも一つは真でなければならない。そうでなければ,コンパイル時エラーが発生する。
throw
文は,try
文(14.18)のtry
ブロックに含まれ,Expression の型は,try
文の少なくとも一つのcatch
節の仮引数の型に代入可能(5.2)とする。(この場合,投げられた値はtry
文によって
捉えられた(caught),という。) throw
文は,メソッド又はコンストラクタ宣言に含まれ,Expression の型は,その宣言のthrows
節(8.4.4,8.6.4)に列挙される型の少なくとも一つの型に代入可能(5.2)とする。
throw
文は,最初に,Expression を評価する。その Expression の評価が何らかの理由で中途完了すれば,throw
は,その理由によって中途完了する。Expression の評価が,値Vを生成して正常完了すれば,throw
文は,値Vを伴うthrow
という理由で中途完了する。
したがって,throw
文は常に中途完了するとみなすことができる。
throw
文をtry ブロックに含むtry
文(14.18)が存在すれば,それらの try
文のすべての finally
節は,制御が外に移るときに,投げられた値が捉えられるまで実行される。
finally
節の中途完了は,throw
文によって開始された制御の移動を中止することもあることに注意すること。
throw
文が,あるメソッド宣言に含まれるが,その値がそれを含むあるtry
文によって捉えられなければ,そのメソッドの呼出しは,そのthrow
のために中途完了する。
throw
文が,あるコンストラクタ宣言に含まれるが,その値がそれを含むあるtry
文によって捉えられなければ,そのコンストラクタを呼び出す,クラスのインスタンス生成の式(又はクラスClass
のメソッドnewInstance
の呼出し)は,そのthrow
のために中途完了する。
throw
文が静的初期化子(8.5)に含まれれば,コンパイル時の検査が,その値が常に検査されない例外か,又はその値がそれを含むtry
文によって常に捉えられるかのいずれかを保証する。この検査にもかかわらず,その値がそのthrow
文を含むtry
文に捉えられない場合,その値は,それがクラスError
又はそのサブクラスのインスタンスならば,再び投げられる(12.4.2)。そうでなければ,オブジェクトExceptionInInitializerError
に包まれて,投げられる。
慣例によって,ユーザ宣言されたThrowable
は,通常,クラス Throwable
(11.5,20.22)のサブクラスであるクラスException
のサブクラスとして宣言される。
synchronized
文synchronized
文は,実行中のスレッドに代わって相互排他ロック(17.13)を獲得して,ロックを実行し,ロックを解除する。実行中のスレッドがロックを所有している間,他のいかなるスレッドもロックを獲得できない。
SynchronizedStatement:synchronized (
Expression)
Block
Expression の型は,参照型でなければならない。そうでなければ,コンパイル時エラーが発生する。
synchronized
文は,最初に Expression を評価することによって実行される。
Expression の評価が,何らかの理由によって中途完了すれば,synchronized
文は同じ理由によって中途完了する。
そうでないときには,Expression の値がnull
ならば,NullPointerException
が投げられる。
そうでなければ,Expression の非null
の値をVとする。実行中のスレッドは,Vに結びつけられるロックをかける。それから,ブロック(Block) が実行される。ブロック(Block)
の実行が正常完了すれば,ロックは解除され,synchronized
文は正常完了する。
ブロック(Block) の実行が中途完了すれば,ロックが解除されsynchronized
文は同じ理由によって,中途完了する。
あるオブジェクトに結びついたロックの獲得は,それ自体,他のスレッドがそのオブジェクトのフィールドにアクセスしたり,非同期のメソッドをそのオブジェクトに対して起動したりするのを妨げはしない。他のスレッドも,相互排他を実現するために通常の方法で,メソッドsynchronized
又はsynchronized
文を使用することができる。
synchronized
文によって獲得されたロックは,メソッド synchronized
によって暗黙のうちに獲得されるロックと同じとする。8.4.3.5を参照のこと。一つのスレッドは,一つ以上のロックを保持できる。例を次に示す。
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
節に最初に制御が渡されるかどうかに関わらず,そのコードブロックが実行される。
TryStatement:try
BlockCatches
try
BlockCatchesopt
Finally Catches: CatchClause Catches
CatchClause CatchClause:
catch (
FormalParameter)
Block Finally:finally
Block
ここに示したことを明確にするために,次を再び8.4.1から示す。
FormalParameter:
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
節の Block とする。仮引数の名前は,その catch
節の Block 内の局所変数又は例外仮引数として再宣言されてはならない。すなわち,例外仮引数の隠ぺいは許されない。
例外仮引数は,限定名 (6.6) で参照できない。単純名に限る。
例外ハンドラは,左から右の順に考慮される。最初の可能なcatch
節が,投げられた例外オブジェクトを実引数として受け取って,例外を捕捉する。
節は,try
ブロック又はcatch
ブロックから制御がどのように離れたかに関わらず,try
ブロック及び実行されたかもしれないどのcatch
ブロックよりも後に,finally
ブロックが実行されることを保証する。
ブロックの処理は,少し複雑となる。そのため,try
文をfinally
ブロックをもつ場合及びもたない場合の二つに分けて示す。
try-catch
の実行
finally
ブロックをもたないtry
文は,最初にtry
ブロックの実行によって実行される。そのとき,次の選択が存在する。
try
ブロックの実行が正常完了した場合,何の動作も起こらず,try
文は正常完了する。
try
ブロックの実行が値Vのthrow
のために中途完了した場合,次の選択が存在する。
try
文のいくつかのcatch
節の仮引数に代入可能ならば(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
try-catch-finally
の実行
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
を実行しているスレッドであって,テストプログラムの唯一のスレッドが,捕捉されない例外 (20.21.31) のために終了し,例外名及び簡単な処理の軌跡を印字する。
ある文が,到達不能(unreachable) なために実行不可能なとき,コンパイル時エラーが発生する。すべてのJavaコンパイラは,すべての文が到達可能なことを保証するために,ここで示される保守的なフロー解析を行わなければならない。
14.19では,“到達可能”という言葉の厳密な規定を行う。それは,文を含むコンストラクタ,メソッド又は静的初期化子の初めから,その文自体に至るいくつかの実行経路が必ず存在しなければならないこととする。解析は,文の構造を考慮に入れる。条件式が定数値 true
をもつときの,while
,do
及び for
文の特別な扱いを除き,式の値はフロー解析では考慮に入れない。例えば,Javaコンパイラは次のコードを受理する。
{ int n = 5; while (n > 7) n = 2; }
コンパイラの受理は,n
の値がコンパイル時に分かっており,k
への割当てが決して実行されないことを理論的に知ることができるとしても行われる。Javaコンパイラは,14.19で述べる規則にしたがって動作しなければならない。
ここでの定義は,文が到達可能なときに限り,その文が正常完了することを可能とする。
switch
ブロックではない空のブロックは,それが到達可能なときに限り,正常完了可能とする。switch
ブロックではなく空でもないブロックは,その内の最後の文が正常完了可能なときに限り,正常完了可能とする。switch
ブロックではなく空でもないブロック内の最初の文は,そのブロックが到達可能なときに限り,到達可能とする。switch
ブロックではなく空でもないブロック内の他のいずれの文 S も,S に先行する文が正常完了可能なときに限り,到達可能とする。
含まれる文は,そのラベル付き文が到達可能なときに限り,到達可能とする。
if
文は,それが else
部をもつかどうかに関わらず,通常とは違う方法で扱う。このため,これは14.19の最後に別に示す。
switch
文は,次の少なくとも一つが真のときに限り正常完了可能とする。
switch
ブロックは, switch
文が到達可能なときに限り,到達可能とする。 switch
ブロック内の文は,その switch
文が到達可能であって,さらに次の少なくとも一つが真のときに限り到達可能とする。
while
文は,次の少なくとも一つが真のときに限り,正常完了可能とする。
含まれる文は,while
文が到達可能であって,条件式が値 false
の定数式なときに限り,到達可能可能とする。
do
文は,次の少なくとも一つが真のときに限り,正常完了可能とする。
含まれる文は,do
文が到達可能なときに限り到達可能とする。
for
文は,次の少なくとも一つが真のときに限り,正常完了可能とする。
含まれる文は,for
文が到達可能であって,条件式が値 false
な定数式のときに限り,到達可能とする。
含まれる文は,synchronized
文が到達可能なときに限り,到達可能とする。
try
文は,次の二つがともに真のときに限り,正常完了可能とする。
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
の定数式ではないときに限り,到達可能とする。
この方法は,Javaにおける他の制御構造の扱いと一貫性がある。しかし,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。)