目次 | | | 索引 Java言語規定
第2版

14. ブロック及び文

プログラムの実行系列は,文(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)でなければならないという要求を示す。

14.1 文の正常完了及び中途完了

すべての文は,計算ステップを実行する正常実行モードをもつ。以降の節では,各種類の文の正常実行モードを示す。

すべてのステップが中途完了の表示なしに実行されれば,文は,正常完了(complete normally)したという。しかし,次のイベントが文の正常完了を妨げることもある。

これらのイベントが発生した場合には,正常実行モードでの全ステップが完了する前に,一つ以上の文の実行を終了してもよい。それらの文は中途完了(complete abruptly)したという。

中途完了は,常に次の内の一つの関連理由(reason)をもつ。

"正常完了"及び"中途完了"という用語は,式(15.6)の評価においても同じく適用される。式が中途完了する理由は,値付きのthrow(14.17),実行時の例外又はエラー(11., 15.6)のいずれかによる例外の発生に限られる。

文中で式が評価される場合,式の中途完了は常に即座に文の中途完了を同じ理由で引き起こす。正常実行モードにおける引き続いた実行ステップのすべては実行されない。

14.においては,特に指定しない限り,副文の中途完了は,文そのものの中途完了を即座に同じ理由で引き起こし,その文の正常実行モードにおける残りの実行ステップのすべては実行されない。

別途指定しない限り,すべての式の評価及びすべての副文の実行が正常完了すると,文も正常完了する。

14.2 ブロック

ブロック(block)は,波括弧で括られた一連の文,局所クラス宣言,及び局所変数宣言文とからなる。

ブロックの実行では,各局所変数宣言文及びその他の文は,順番に初めから終わりまで(左から右へ)実行される。これらすべてのブロック内の文が正常完了すると,ブロックも正常完了する。これらのうちの文のどれかが,何らかの理由で中途完了すると,同じ理由でブロックも中途完了する。

14.3 局所クラス宣言

局所クラスは,どのクラスのメンバでもない,名前をもつ入れ子クラス(8)とする。すべての局所クラスは内部クラス(8.1.2)とする。各局所クラス宣言文はブロックに直接含まれる。局所クラス宣言文はブロック内の他の周囲の文と自由に混ぜてもよい。

ブロック内で宣言された局所クラスの有効範囲は,直接取り囲むブロックの残りであり,それ自身のクラス宣言を含む。

局所クラス C の名前は,C の有効範囲内の,直接取り囲むメソッド,コンストラクタ,又は初期化子ブロックの局所クラスとして再宣言してはならない。再宣言するとコンパイル時エラーとなる。しかしながら,局所クラス宣言は,局所クラス宣言の有効範囲内で入れ子となったクラス宣言の内部ではおおい隠される(6.3.1)。局所クラスは正準名をもたないし,完全限定名ももたない。

局所クラス宣言が,publicprotectedprivate 又は static のアクセス修飾子のうち一つでも含むならば,コンパイル時エラーとする。

以上の規則の幾つかの側面を以下の例で示す。

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 の4番目と最後の宣言は,それ以前の Local の宣言の有効範囲の外にあるため,認められている。

14.4 局所変数宣言文

局所変数宣言文(local variable declaration statement)は,一つ以上の局所変数名を宣言する。

ここでの記述を更に明瞭にするために,8.3での記述をここで繰り返す。

すべての局所変数宣言文は,ブロックに直接含まれる。局所変数宣言文はブロックにおける他の種類の文と自由に混在してよい。

局所変数宣言は,for(14.13)の先頭に置かれることもある。この場合,それが局所変数宣言文の一部であったかのように実行される。

14.4.1 局所変数の宣言子及び型

局所変数宣言における各宣言子(declarator)は,一つの局所変数を宣言する。宣言子に現れるIdentifierが名前となる。

宣言子の最初にオプションのキーワード final が現れると,宣言される変数は最終変数(4.5.4)となる。

変数の型は,局所変数宣言に現れる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;
これらの配列宣言の"混在した表記法"は望ましくない。

float の局所変数は常に単精度数値集合(4.2.3)の要素を値として含む。同様に,型 double の局所変数は常に倍精度数値集合の要素を値として含む。型 float の局所変数が単精度数値集合の要素ではない単精度指数部拡張数値集合の要素を含んだり,型 double の局所変数が倍精度数値集合の要素ではない倍精度指数部拡張数値集合の要素を含むことはどちらも許されない。

14.4.2 局所変数宣言の有効範囲

ブロック内で宣言された局所変数の有効範囲(14.4.2)は,変数そのものの初期化子(14.4)から始まり,局所変数宣言文中の右側の他の宣言子を含む,ブロックの残り部分とする。

局所変数 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

14.4.3 局所変数による名前のおおい隠し

局所変数として宣言された名前が,フィールド名として既に宣言されていれば,その外のフィールド名としての宣言は,局所変数の有効範囲ではおおい隠される(6.3.1)。同様に,名前が変数又は仮引数名として既に宣言されていれば,その外の変数又は仮引数名としての宣言は,(おおい隠しにより14.4.2の規則のもとでコンパイル時エラーが発生しないことを条件として)局所変数の有効範囲ではおおい隠される。おおい隠された名前は,適当な限定名を用いることによって,アクセス可能である場合もある。

例えば,キーワード this を用い,形式 this.x によって,おおい隠されたフィールド x にアクセスできる。実際,次の形式がコンストラクタ(8.8)に典型的に出現する。

class Pair {
	Object first, second;
	public Pair(Object first, Object second) {
		this.first = first;
		this.second = second;
	}
}
この例では,コンストラクタは,初期化されるフィールドと同じ名前の仮引数をもつ。これは,仮引数ごとに異なる名前を生成しなければならない場合よりは簡単であって,この様式化された文脈においては,さほど混乱を生じない。しかし,一般には,フィールドと同じ名前を局所変数に割り当てることは,よい書き方ではない。

14.4.4 局所変数宣言の実行

局所変数宣言文は,実行可能文とする。実行のたびに,宣言子は,左から右の順番に処理される。宣言子が初期化式をもっていれば,その式は評価され,その値が変数に割り当てられる。宣言子が初期化式をもっていない場合には,Javaコンパイラは16で与えられたアルゴリズムを使って,すべての変数参照について,値の割当ての実行が先行していることを検証しなければならない。検証に失敗したら,コンパイル時エラーが発生する。

第1番目を除いて,各初期化は,その前の初期化式の評価が正常完了した場合にだけ,実行される。局所変数宣言の実行は,最後の初期化式の評価が正常完了して初めて,正常完了する。局所変数宣言が初期化式を全く含まなければ,常にその実行は正常完了する。

14.5 文

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を再掲する。

したがって,文は文法的に二つに分類される。else 節をもたない if 文("短縮 if 文")で終了する文,及び明らかにそれとは違う文である。短縮 if 文として明らかに終わらない文だけが,else 節をもつ if 文のキーワード else の直前の副文として現れてよい。

この単純な規則で"ぶら下がりelse"問題が防げる。"短縮 if 文禁止"という制限をもつ文の実行の振る舞いは,"短縮 if 文禁止"という制限をもたない場合の,同じ種類の文の実行の振る舞いと同じとする。この区分は,文法上の困難さを解決するためだけに用いる。

14.6 空文

空文(empty statement)は,何もしない。

空文の実行は,常に正常完了する。

14.7 ラベル付き文

文は,ラベル(label) 接頭語をもつことができる。

ここで 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 が他の理由で中途完了した場合には,ラベル付き文も同じ理由で中途完了する。

14.8 式文

ある種の式は,その後にセミコロンが続くことによって文として使われる。

式文(expression statement)は,式を評価することによって実行される。式が値をもっても,その値は放棄される。式文の実行が正常完了するのは,式の評価が正常完了するのと等価とする。

C及びC++ と異なり,Javaプログラム言語では,限られた形の式だけが式文として使われる。Javaプログラム言語では void という型はないので,"出力を型 void にキャストする"という伝統的なCでの,次の式文の書き方は許されないことに注意する必要がある。

(void) ... ;			// incorrect!
一方,Javaは,式文においてすべての最も有益な種類の式を許す。またJavaでは,型 void のメソッドを呼び出すために,式文としてメソッド呼出しを使う必要がない。したがって,前述の工夫は,ほとんど必要ない。この工夫が必要なら,値割当て文(15.26) 又は局所変数宣言文(14.4)のいずれかを,その代りに使用できる。

14.9 if

if 文は,文の条件付きの実行,又は2文の条件付き選択の内,両方ではなく,どちらか一方だけの実行の選択を可能とする。

Expression は,boolean型でなければならない。そうでなければ,コンパイル時エラーが発生する。

14.9.1 if-then

if-then 文の実行では,最初に Expression を評価する。Expressionの評価が何らかの理由で中途完了すると,if-then 文も同じ理由で中途完了する。そうでなければ,式の評価の結果生じる値に基づいて選択が行われる。

14.9.2 if-then-else

if-then-else 文は,最初に Expression を評価することによって実行される。Expression の評価が何らかの理由で中途完了すれば,if-then-else 文も同じ理由で中途完了する。そうでなければ,式の評価の結果生じる値に基づいて,選択が行われる。

14.10 switch

switch文は,式の値に応じて幾つかの文のいずれかへ制御を移す。

Expression の型は,charbyteshort 又は int のいずれかでなければならない。そうでなければ,コンパイル時エラーが発生する。

switch 文の本体は switch ブロック(switch block)として知られる。switch ブロックに直接含まれる文は,一つ以上の case ラベル又は default ラベルによってラベル付けされてもよい。case ラベルにおける定数式(15.28)の値と共に,これらのラベルは,switch 文に関連している(associated) という。

次のすべては,真でなければならない。そうでなければ,コンパイル時エラーが生じる。

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 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 の値との比較を実行する。それから,選択が行われる。

switch 文の Block 本体に直接含まれる文が中途完了した場合には,次のとおりに扱われる。

C及びC++ と同様,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

14.11 while 文

while 文では,Expression の値が false になるまで,Expression 及び Statement を繰り返し実行する。

Expressionboolean 型でなければならない。そうでなければ,コンパイル時エラーが発生する。

while 文の実行では,最初に Expression が評価される。Expression の評価が何らかの理由で中途完了すれば,while 文も同じ理由で中途完了する。そうでなければ,得られる値に基づいて次の選択をする。

初めて評価されるときに,Expression の値が false ならば,Statement は実行されない。

14.11.1 中途完了

含まれる Statement の中途完了は,次のように扱われる。

14.12 do 文

do 文では Expression の値が false になるまで,Expression 及び Statement を繰り返し実行する。

Expressionboolean 型でなければならない。そうでなければ,コンパイル時エラーが発生する。

do 文の実行では,最初に Statement が実行される。そのとき,次の選択が存在する。

do 文の実行では,含まれる Statement は常に少なくとも 1 度は実行される。

14.12.1 中途完了

含まれる Statement の中途完了は,次のとおりに扱われる。

14.12.2 do 文の例

次のコードは,クラス Integer のメソッド toHexString の一つの実装例である。

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 文は適切な制御構造である。

14.13 for

for 文は,初期化コードを実行し,Expression の値が false となるまで,ExpressionStatement 及び更新のコードを繰返し実行する。

Expression は,boolean 型でなければならない。そうでなければ,コンパイル時エラーが発生する。

14.13.1 for 文の初期化

for 文の実行は,最初に ForInit コードの実行によって行われる。

14.13.2 for 文の繰返し

for 文における繰返しは,次のとおりとする。

Expression の値が最初の評価で false であれば,Statement は実行されない。

Expression が存在しなければ,break 文を使用したときだけ,for 文は正常完了可能とする。

14.13.3 for 文の中途完了

含まれる Statement の中途完了は,次の方法で扱う。

14.14 break

break 文は,取り囲んでいる文の外側に制御を移す。

ラベルなしの break 文は,直接取り囲んでいるメソッド又は初期化子ブロック内の,取り囲んでいる最も内側の switchwhiledo 又は for 文に制御を移そうとする。この文は break ターゲット(break target)と呼ばれ,直ちに正常完了する。

正確には,ラベルなし break 文は,いつもラベルなし break であるという理由で中途完了する。いかなる switchwhiledo 又は for 文も,break 文を取り囲んでいなければ,コンパイル時エラーが発生する。

ラベル Identifier 付きの break 文は,それを取り囲んでいる,同じ Identifier をラベルとする,ラベル付き文(14.7)に制御を移そうとする。この文は break ターゲット(break target)と呼ばれ,直ちに正常完了する。この場合,break ターゲットは,whiledofor 又は 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 への辺に対する検査の間で共有されるリストを一つの辺を除いてコピーする,というコードを可能にする。

14.15 continue

continue 文は,whiledo 又は for 文内にだけ出現してよい。これら3種類の文を,繰返し文(iteration statements)と呼ぶ。制御は,繰返し文のループ継続箇所に渡る。

ラベルなし continue 文は,直接取り囲んでいるメソッド又は初期化子ブロック内の,取り囲んでいる最も内側の whiledo 又は for 文に制御を移そうとする。この文は,continue ターゲット(continue target)と呼ばれ,直ちに現在の繰返し処理を終了し,新たな繰返し処理を開始する。

正確には,この continue 文は,ラベルなし continue という理由で常に中途完了する。直接取り囲んでいるメソッド又は初期化子ブロック内の,いかなる whiledo 又は for 文もその continue 文を取り囲まなければ,コンパイル時エラーが発生する。

ラベル Identifier 付き continue 文は,それを取り囲んでいる,同じ Identifier をラベルとする,ラベル付き文(14.7)に制御を移そうと試みる。その文は,continue ターゲット(continue target)と呼ばれ,直ちに現在の繰返し処理を終了し,新たな繰返し処理を開始する。continue ターゲットは,whiledo 又は 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);
	}
}
この例及び前述の例のどちらでもよいときに,どちらを使うかは,プログラムの書き方の問題となる。

14.16 return

return文は,メソッド(8.415.12)又はコンストラクタ(8.815.9)の呼出し元へ制御を返す。

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 文によって開始された制御の移動を中止することがある。

14.17 throw

throw 文は,投げられることになる例外(11)を引き起こす。その結果は,投げられた値をとらえる try(14.19) が見つかるまでの,複数の文及び複数のコンストラクタ,インスタンス初期化子,静的初期化及びフィールド初期化子の評価,及びメソッド呼出しを終了することもある制御のすみやかな移動 (11.3) である。try 文が見つからなければ,throw を実行したスレッド(17.)の実行は,そのスレッドが属するスレッドグループに対するメソッド uncaughtException の呼出しの後,終了する(11.3)

throw 文内の Expression は,型 Throwable に代入可能な(5.2)参照型の変数又は値を指定しなければならない。そうでなければ,コンパイル時エラーが発生する。さらに,次の3条件の内,少なくとも一つは真でなければならない。そうでなければ,コンパイル時エラーが発生する。

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の下位クラスとして宣言される。

14.18 synchronized

synchronized 文は,実行中のスレッドに代わって相互排他ロック(17.13)を獲得して,ブロックを実行し,ロックを解除する。実行中のスレッドがロックを所有している間,他のいかなるスレッドもロックを獲得できない。

Expression の型は,参照型でなければならない。そうでなければ,コンパイル時エラーが発生する。

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!
単一スレッドが複数回ロックをかけられなければ,この例はデッドロックになる。

14.19 try

try 文は,ブロックを実行する。値が投げられ,try 文がそれを捕捉できる一つ以上の catch 節をもつとき,制御はそれらの最初の catch 節に移される。その try 文が finally 節をもてば,try ブロックが正常完了するか中途完了するかに関わらず,さらには,どれかの catch 節に先に制御が渡されたかどうかに関わらず,finally 節のコードブロックが実行される。

ここに示したことを明確にするために,次を再び8.4.1から示す。

ここに示したことを明確にするために,次を再び8.3から示す。

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 ブロックをもつ場合及びもたない場合の二つに分けて示す。

14.19.1 try-catch の実行

finally ブロックをもたない 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

14.19.2 try-catch-finally の実行

finally ブロックをもつ try 文は,最初に try ブロックの実行によって実行される。そのとき,次の選択が存在する。

次に例を示す。

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 の一種) は,maintry 文には捕捉されない。これは,NullPointerExceptionBlewIt 型の変数に割り当てられないことによる。これによって finally 節が実行され,その後,main を実行しているスレッドであって,テストプログラムの唯一のスレッドが,捕捉されない例外のために終了し,通常は例外名及び簡単な処理の軌跡を印字する。

14.20 到達不能文

ある文が,到達不能(unreachable) なために実行不可能なとき,コンパイル時エラーが発生する。すべてのJavaコンパイラは,すべての文が到達可能なことを保証するために,ここで示される保守的なフロー解析を行わなければならない。

14.20では,"到達可能"という言葉の厳密な規定を行う。それは,文を含むコンストラクタ,メソッド,インスタンス初期化子又は静的初期化子の初めから,その文自体に至る何らかの実行経路が必ず存在しなければならないこととする。解析は,文の構造を考慮に入れる。条件式が定数値 true をもつときの,whiledo 及び for 文の特別な扱いを除き,式の値はフロー解析では考慮に入れない。

例えば,Javaコンパイラは次のコードを受理する。

{
	int n = 5;
	while (n > 7) k = 2;
}
コンパイラの受理は,n の値がコンパイル時に分かっており,k への代入が決して実行されないことを理論的に知ることができるとしても行われる。

Javaコンパイラは,14.20で述べる規則にしたがって動作しなければならない。

14.20の規則は,次の二つの技術用語を定義する。

ここでの定義は,文が到達可能なときに限り,その文が正常完了することを可能とする。

規則は次のとおりとする。

if 文の実際の規則は次のとおりとする。

例として,次の文はコンパイル時エラーを引き起こす。

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版