Java : for 文の構文をもっかい考える
Java における単純な for 文の構文をもっかいおさらいする。言語仕様的にできることを見直して、可能性を把握しておく。
for 文の基本構文
for( 初期化式 (initialization); 条件式 (condition); 継続式(increment/decrement) ) {
// 処理
}
これが基本構文。
List<String> list; // 要素がいっぱい入っているテイ
for(int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
こんな for 文はよく見かけるだろう。
for 文のカッコの中のセミコロンで区切られた3ヶ所は、それぞれ以下のような役割を持つ。
- 初期化式部分で変数を初期化する
- 条件式部分に書かれた条件が true の間、for ブロック内の処理を繰り返す
- 継続式部分に書かれた処理は for ブロックの最後に辿り着いた時に行われる処理になる
この3つが、3行のコードが記述できる特殊な場所、ということをよくよく考えると、変わったことができることに気が付く。
初期化式を複数記述する
初期化式はカンマで区切って複数記述できる。たとえば先ほどの List<String>
をループで回すサンプルに対し、初期化式を複数書くと、こうなる。
for(int i = 0, length = list.size(); i < length; i++) {
System.out.println(list.get(i));
}
list.size()
を length
という int 型の変数に格納している。条件式部分も合わせてその length
変数を見るようにした。こうすると list.size()
メソッドの呼び出し回数が減らせる。for ループの高速化テクニックとしてよく見かける部類に入ると思う (高速化の効果としてはたかが知れてるっぽい)。
無論、このサンプル程度の内容なら拡張 for 文 (for-each) の使用を検討した方が良いが、今回は拡張 for 文の話はしない。
for(String str : list) {
System.out.println(str);
}
初期化式がなくてもいい
初期化式部分は、「初期化式」という名のとおり、セミコロンで文が終わるまで、カンマで区切って複数の変数に初期値を与えることができる。要するにさっきの例はこう書いても良い。
int i = 0,
length = list.size();
for( ; i < length; i++) {
System.out.println(list.get(i));
}
何の式も書かない場合であっても、セミコロンの数は合ってないとダメ。
初期化式は変数宣言を外出ししておけば型が違ってもいい
初期化式部分は変数の初期値宣言の行と同じ考え方ができる。ということは、どういう時に使えるかは分からないが、一応、こんなこともできる。
int i;
double j;
String str;
for(i = 0, j = 5.5, str = "Hoge"; i < list.size(); i++) {}
条件式は複数条件組み合わせられる
初期化式が複数書けるところから想像できたかもしれないが、条件式も複数の条件を組み合わせられる。
これも良い例がないが、言語仕様上はこんなことができる。
for(int i = 0, j = 10; i < 5 && j > 4; i++) {
System.out.println(i + ", " + j);
j -= 2;
}
// 出力結果 :
// 0, 10
// 1, 8
// 2, 6
条件式部分が true になる間だけループを繰り返すので、AND &&
条件で変数 i
と j
がそれぞれ5未満、4超えを満たす間だけループさせてみた。
条件式を使って break
っぽくループを抜けてみる
以下のような break
を使ってループを抜けるような処理を、条件式部分を使ってやってみる。
// list 中の要素に "DDD" が出てきたらループを抜けるサンプル
List<String> list = new ArrayList<String>(Arrays.<String>asList("AAA", "BBB", "CCC", "DDD", "EEE"));
for(int i = 0; i < list.size(); i++) {
if(list.get(i).equals("DDD")) {
break;
}
System.out.println(list.get(i));
}
// 実行結果 :
// AAA
// BBB
// CCC
条件式部分を使うと、こうなる。
// list は上のサンプルと同じで
for(int i = 0; !list.get(i).equals("DDD"); i++) {
System.out.println(list.get(i));
}
// 実行結果 : 上に同じ
条件式部分は、その式を boolean に変換でき、それが true である間だけループを回す継続条件になっているので、否定の論理演算子「!
」で「"DDD" とイコールではない」間だけループさせるようにしている。
条件式を使わないこともできるか
やっぱり break
を使う構文に戻って考えてみると、有効利用できそうな機会が思いつかないが、条件式を空にすることもできる場合がある。
for(int i = 0; ; i++) {
if(list.get(i).equals("DDD")) {
break;
}
System.out.println(list.get(i));
}
必ず break
できる条件が用意できてないと無限ループか IndexOutOfBoundsException
あたりになるだろうけど。
やっぱり継続式も複数書ける
継続式は for ブロックの最後に行われる行が先頭に来ているもの、と思うと、色んなことができる。少し手前に出したサンプルを少し変えるとこんな感じ。
for(int i = 0, j = 10; i < 5; i++, j -= 2) {
System.out.println(i + ", " + j);
}
// 出力結果 :
// 0, 10
// 1, 8
// 2, 6
// 3, 4
// 4, 2
意味分かんないコードだけどこんなこともできる。
int i;
String str;
for(i = 1, str = "" + i; i <= 5; i = i + 2 - 1, str = str + " " + i) {
System.out.println(i + " : " + str);
}
// 実行結果 :
// 1 : 1
// 2 : 1 2
// 3 : 1 2 3
// 4 : 1 2 3 4
// 5 : 1 2 3 4 5
やっぱり継続式がなくてもいい
継続式を使わずに while 文チックに書いてみたり。
// list の要素を最後から順に表示
for(int i = list.size(); i != 0; ) {
System.out.println(list.get(i - 1));
--i; // デクリメント
}
インクリメントやデクリメントをしない継続式
Random rnd = new Random();
for(int i = rnd.nextInt(10); i != 0; i = rnd.nextInt(10)) {
System.out.println(i);
}
Random#nextInt()
は、「0」から「引数に与えた数値 - 1」までの数値をランダムに返すメソッド。nextInt(10)
とすれば、0 から 9 のいずれかの数値が返される。
これを利用し、継続式部分でランダムな数値を生成させてみた。結果は「2, 6, 4, 5, 5, 3, 8, …」などランダムな数値が出力され、条件式に合わない 0
が出てきた時に終了する。
Random rnd = new Random();
for( int i = rnd.nextInt(10);
i != rnd.nextInt(10);
i = rnd.nextInt(10) ) {
System.out.println(i);
}
こうすれば条件式に何が設定されたか分からないから、どの値が出るとループを終了するのかも分からなくなる。意味はない。
もう for 文を使わない・あるいは for 文のそれぞれの式の役割を見直す
初期化式、条件式、継続式の役割が分かったところで、いずれの式も使わない for ループを作ってみる。
int i = 0; // 初期化式
for( ; ; ) {
if(i == list.size()) break; // 条件式
System.out.println(list.get(i));
i++; // 継続式
}
条件式は継続条件ではなく終了条件として記述しているが、基本的にこういうこと。for 文の式が使われなくて、心なしか泣いているように見える…。( ; ; )
しかしこれでとりあえず、for 文のカッコの中にどんなことが書けるのか、今までとちょっと違う理解の仕方ができた気がする。