目次 | 前 | 次


14. ブロック及び文

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) でなければならないという要求を示す。

14.1 文の正常完了 (complete normally) 及び中途完了 (complete abruptly)

すべての文は,計算ステップを実行する正常実行モードをもつ。次の節は,各種類の文の正常実行モードを示す。以降では,各種類の文の正常実行モードを示す。すべてのステップが中途完了の表示なしに実行されれば,文は,正常完了したという。しかし,イベントが文の正常完了を妨げることもある。

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

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

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

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

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

14.2 ブロック

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

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

14.3 局所変数宣言文

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

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

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

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

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

局所変数宣言における各 宣言子(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;

これらの配列宣言の“混在した表記法”は望ましくない。

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

ブロック内で宣言された局所変数の有効範囲は,ブロックの残り部分とし,変数そのものの初期化子を含む。局所変数仮引数の名前は,その有効範囲内の局所変数又は例外仮引数として再宣言してはならない。再宣言すると,コンパイル時エラーとなる。すなわち,局所変数の名前を隠すことは許されない。

局所変数は,限定名(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

14.3.3 局所変数による名前の隠ぺい

局所変数として宣言された名前が,フィールド又は型名として既に宣言されていれば,その外のフィールド又は型名としての宣言は,局所変数の有効範囲では隠ぺいされる。それらのフィールド又は型名も,適当な限定付きの名前を用いることによって,ほとんど常に(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;
	}
}

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

14.3.4 局所変数宣言の実行

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

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

14.4 文

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 文に属するものと勝手に決めてしまう。この規則は,次の文法で明らかにされている。

次に,ここでの記述を更に明瞭にするために,14.8を再掲する。

したがって,文は文法的に二つに分類される。else 節を持たない if 文(“短縮 if 文“)で終了する文,及び明らかにそれとは違う文である。短縮 if 文として明らかに終わらない文だけが,else 節をもつ if 文のキーワード else の直前の副文として現れてよい。この単純な規則で“ぶら下がりelse”問題が防げる。“短縮 if 文禁止”という制限をもつ文の実行の振舞いは,“短縮 if 文禁止”という制限をもたない場合の,同じ種類の文の実行の振舞いと同じとする。この区分は,文法上の困難さを解決するためだけに用いる。

14.5 空文

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

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

14.6 ラベル付き文

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

ここで 識別子(Identifier) は,直接含まれる 文(Statement) のラベルと宣言される。

C及びC++ と異なり,Java言語は,goto 文をもつていない。文のラベル識別子は,ラベル付き文のどこにでも出現可能な,break 文 (14.13)又は continue 文(14.14)によって使われる。

識別子によってラベル付けされた文は,同じ識別子によってラベル付けされた他の文内には現れてはならない。現れた場合は,コンパイル時エラーが発生する。文がお互いに包含関係になければ,2文が同じ識別子でラベル付けされてもよい。

同じ識別子をラベル,パッケージ,クラス,インタフェース,メソッド,フィールド,仮引数又は局所変数の名前として使うことに対する制限はない。文にラベル付するために使用した識別子は,パッケージ,クラス,インタフェース,メソッド,フィールド,仮引数及び局所変数が同じ名前を用いても隠ぺいしない。局所変数や例外ハンドラ(14.18) の仮引数としての同じ名前の識別子が使用されても,それは文のラベルを隠ぺいしない。

ラベル付き文は,直接含まれた Statement の実行によって実行される。文が Identifier によってラベル付けされ,その含む Statement が同じ Identifier をもつ break 文によって中途完了すれば,ラベル付き文は正常完了する。Statement が他の理由で中途完了した場合には,ラベル付き文も同じ理由で中途完了する。

14.7 式文

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

式文は,式を評価することによって実行される。式が値をもっても,その値は放棄される。

式文の実行が正常完了するのは,式の評価が正常完了するのと等価とする。

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

(void) ... ; 		// This idiom belongs to C, not to Java!

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

14.8 if 文

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

式(Expression) は,論理型をもたなければならない。そうでなければ,コンパイル時エラーが発生する。

14.8.1 if-then 文

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

14.8.2 if-then-else 文

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

14.9 switch文

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

式(Expression)の型は,char,byte,short 又は int のいづれかでなければならない。そうでなければ,コンパイル時エラーが発生する。

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

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

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

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

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

14.10 while 文

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

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

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

初めて評価されるときに,式の値が偽ならば,文は実行されない。

14.10.1 中途完了

含まれる文の中途完了は,次によって扱われる。

14.11 do 文

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

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

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

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

14.11.1 中途完了

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

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

14.12 for 文

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

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

14.12.1 初期化

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

14.12.2 繰返し

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

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

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

14.12.3 中途完了

Statement の中途完了は,次の方法で扱う。

14.13 break 文

break 文は,囲まれた文の外側に制御を移す。

ラベル無しの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への枝に対する検査,の間で共有されるリストを一つの枝を除いてコピーする,というコードを可能にする。

14.14 continue 文

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

ラベル無し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文は,常に中途完了するとみなすことができる。

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);
	}
}

この例及び前述の例のどちらでもよいときに,どちらを使うかは,プログラミング書式の問題となる。

14.15 return 文

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

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

14.16 throw 文

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

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


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

14.17 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.5を参照のこと。一つのスレッドは,一つ以上のロックを保持できる。例を次に示す。


class Test {
	public static void main(String[] args) {
		Test t = new Test();
		synchronized(t) {
			synchronized(t) {
				System.out.println("made it!");
			}
		}
	}
}

この例は,次を出力する。

made it!

この例では,単一のスレッドが一度以上のロックをかけられなければ,デッドロックになる。

14.18 try 文

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

ここに示したことを明確にするために,次を再び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 節の Block とする。仮引数の名前は,その catch 節の Block 内の局所変数又は例外仮引数として再宣言されてはならない。すなわち,例外仮引数の隠ぺいは許されない。

例外仮引数は,限定名 (6.6) で参照できない。単純名に限る。

例外ハンドラは,左から右の順に考慮される。最初の可能なcatch節が,投げられた例外オブジェクトを実引数として受け取って,例外を捕捉する。

finally節は,tryブロック又はcatchブロックから制御がどのように離れたかに関わらず,tryブロック及び実行されたかもしれないどのcatchブロックよりも後に,finallyブロックが実行されることを保証する。

finallyブロックの処理は,少し複雑となる。そのため,try文をfinallyブロックをもつ場合及びもたない場合の二つに分けて示す。

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

14.19 到達不能文

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

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

{
	int n = 5;
	while (n > 7) n = 2;
}

コンパイラの受理は,n の値がコンパイル時に分かっており,k への割当てが決して実行されないことを理論的に知ることができるとしても行われる。Javaコンパイラは,14.19で述べる規則にしたがって動作しなければならない。

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

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

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



含まれる文は,そのラベル付き文が到達可能なときに限り,到達可能とする。



含まれる文は,while 文が到達可能であって,条件式が値 false の定数式なときに限り,到達可能可能とする。



含まれる文は,do 文が到達可能なときに限り到達可能とする。



含まれる文は,for 文が到達可能であって,条件式が値 false な定数式のときに限り,到達可能とする。



含まれる文は,synchronized 文が到達可能なときに限り,到達可能とする。

if 文が,次の方法で扱われることは期待してもよいが,これらはJavaが実際に使っている規則ではない。



then 文は,if-then 文が到達可能であって,条件式の値が false の定数式ではないときに限り,到達可能とする。

この方法は,Javaにおける他の制御構造の扱いと一貫性がある。しかし,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。)



目次 | 前 | 次