3.7 ブロック
3.7.1 文の並び
これまで解説してきた文のうち、if 文、while 文、do 文、for 文には、抱合する文を 1 つしか指定することができないという致命的な問題があります。例えば、if 文の構文を再びみてみましょう。
if (条件式) 文 else 文
このとき、実行する文は 1 つまでしか指定することができません。else の手前の文や直後の文に複数の文を指定することはできないのです。例えば、次のプログラムをコンパイルするとエラーとなってしまうでしょう。
if (bValue) bValue = false; System.out.println("..."); else bValue = true;
一見、問題がないように思えてしまうかもしれませんが、if に対応する文は bValue = false; の式文だけであり、その直後の println() メソッドの呼び出しは、すでに if 文を抜け出しています。そのため、コンパイラは else キーワードに対応する if を見つけることができません。while 文、do 文、for 文も同様で、繰り返し対象となる文は 1 つしか指定することができないのです。
しかし、多くの場合、1 つの文で処理を終了させることはできません。そこで、複数の文で構成される文の並びを定義することで、この問題を解決することができます。これをブロックと呼びます。
ブロックは、中括弧 { } で囲み、その内部に文や宣言文を指定することができます。制御がブロックに移行すると、内部の文を順番に実行し、すべてのブロック文が終了した時点で制御を戻します。ブロックは 1 つの文として扱うことができるため、流れ制御で効果を発揮するでしょう。次のように記述すれば、if 文による分岐によって複数の文を実行することができます。
if (bValue) { bValue = false; System.out.println("..."); } else { ... }
ただし、あまり複雑にならないよう、可能な限りシンプルに書くべきであるということは意識してください。分岐後のコードがあまりにも長いようであれば、別な手段を検討した方が良いかもしれません。また、ブロックの中はインデントを一段階深くするという暗黙のルールが存在します。これは、「2.4 トークン」で説明した理由に基づきます。
class Test { public static void main(String args[]) { for(int i = 1 ; i < 10 ; i++) { for(int j = 1 ; j < 10 ; j++) { String str = "" + (i * j) + ' '; if (i * j < 10) System.out.print(' '); System.out.print(str); } System.out.print('\n'); } } }
>java Test 1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18 3 6 9 12 15 18 21 24 27 4 8 12 16 20 24 28 32 36 5 10 15 20 25 30 35 40 45 6 12 18 24 30 36 42 48 54 7 14 21 28 35 42 49 56 63 8 16 24 32 40 48 56 64 72 9 18 27 36 45 54 63 72 81
ブロックを使えば、コード1のような複雑な流れ制御を実現することができます。このプログラムは、for 文を使って九九表を表示しています。最初の for 文で宣言されている変数 i は行を、次の for 文で宣言されている変数 j は列を表しています。
流れ制御を行うための文は入れ子関係にすることが可能なので、for 文の繰り返し処理の中でさらに for 文を指定することは可能です。ブロックは文の並びであり、for は文であることからも、入れ子は正当な関係であることがわかります。
3.7.2 ローカル変数の寿命
ブロックは文や宣言文の並びであると定義されているため、ローカル変数宣言文を記述することができます。そこで、宣言した変数はどこまで有効なのかということが問題になります。
コード1では、入れ子になった for 文で宣言されている str 変数が疑問の対象となるでしょう。for 文によってブロックが何度も繰り返し実行されるため、繰り返すたびに str 変数が宣言されているということになります。しかし、一度定義した識別子は重複してはならないというルールも存在します。確かに、ソース上では重複していませんが、ブロックが繰り返されるたびに宣言文が実行されるということは、論理的に重複していると考えることもできるかもしれません。これは、どのように解釈されるべきでしょうか。
ローカル変数宣言文によって作成された変数には、有効範囲というものが定められています。これをローカル変数宣言のスコープと呼びます。ローカル変数宣言のスコープは、ローカル変数宣言文が記述されたブロックの残りの部分に限られています。つまり、ブロックから抜け出した時点で、その変数は自動的に破棄されてしまうのです。
iValue = 10; //まだ宣言されていないのでエラー { int iValue = 0; iValue = 10; //OK } iValue = 100; //スコープの外なのでエラー
同一ブロック内で宣言された変数や、そのブロックの外側で宣言された変数にはアクセスすることができますが、ブロックの外側から、ブロックの中の変数を参照することはできないのです。ローカル変数宣言文は、常に宣言文が記述されたブロックの中でのみ有効だからです。コード1の変数 str は、ブロックの中で宣言されているため、ブロックが繰り返し実行されるたびに新しく生成され、処理が終了した時点でブロックから抜け出し破棄されているのです。
class Test { public static void main(String args[]) { String str1 = "Kitty on your lap"; { String str2 = str1 + "\n\tあなたのひざの上の猫"; System.out.println(str2); } //System.out.println(str2); //エラー } }
>java Test Kitty on your lap あなたのひざの上の猫
コード2は、ローカル変数のスコープを証明するためのプログラムです。変数 str1 は main() メソッドのトップレベルのスコープなので、main() メソッドの内部であればどこからでもアクセスすることができます。一方で、str2 変数はさらに深いブロックで宣言されているため、ブロックの外からアクセスすることはできません。
ブロックを抜け出した後の、コメント化されている一行をコンパイルすれば、エラーになることが確認できるでしょう。ブロックの外の変数にはアクセスすることが可能なので、str2 の初期化子で str1 にアクセスしていることは問題にはなりません。