繰り返し (while), 複雑な条件式

while 文を用いた繰り返し実行

while 文の構造

for の他にもループ文または繰り返し文と呼ばれるものが何種類かある。
今回は、まず while文 を理解する。while文は

while (条件式) {
   文の列
}

の形をしている。この文の列が1つの文だけである場合は、

while (条件式)
  文

の形でも良い。

{ 文の列 }

の形はブロックと呼ばれるが、ブロックも文の一種であるから、while文の形は後者の形であるといっても良い。
while文は、条件式の値がtrueである(条件が成立する)間繰り返し、「文」または「文の列」を実行する文である。

たとえば、

int i = 1;
while ( i < 10 ){
    System.out.println("i = " + i);
    i++;
}

を実行すると、最初はiの値が1であるから i < 10 の値はtrueであり、「i = 1」がプリントされ、i++;が実行されてiの値が2になる。
それでもi < 10 の値はtrueであるから、「i = 2」がプリントされ、i++;が実行されてiの値が3になる。
以下同様に繰り返されて、iの値が10になったときに初めてi < 10 の値はfalseになるので、そこでこのwhile文の実行が終わる。

これはfor文の形でも書くことができ、上記の命令列と同じことをするfor文は以下のように表せる。

for (int i = 1; i < 10; i++) {
    System.out.println("i = " + i);
}

つまり、while文はfor文から「初期化式」を外に出し、「ステップを進める式」を繰り返し実行される文に移したものである。

初期化式やステップを進める式が複雑である場合、while文を用いたほうが良い場合がある。

番兵 (Sentinel)

for文の主な用途は指定回数繰り返すようなループであった。
例えば、次の例では "Hello" と3回表示する。

for (int i = 0; i < 3; i++) {
    System.out.print("Hello");
}

それに対して、while文では回数の指定が行われていない繰り返しを書くときに便利である。
for文は繰り返す条件が"繰り返す回数"に特化されることが多いため、指定回数の繰り返しを記述する際に便利である。

下記は、「0が入力されるまで入力された値を表示するプログラム」の断片である。

...
int input;
// (1)
input = Integer.parseInt(reader.readLine());
// (2)
while (input != 0) {
    // (3)
    System.out.println(input);
    // (4)
    input = Integer.parseInt(reader.readLine());
}
  1. 最初の入力をコンソールから取得している。
  2. 「0が入力されるまで繰り返す」ということを表している。
  3. 入力された値を表示している
  4. 次の入力をコンソールから取得し、繰り返しの次のステップを実行する準備を行っている。

ある変数が特定の値になるまで繰り返すようなwhile文
sentinel-controlled repetition (番兵に監視された繰り返し, ここではinputの値が0でないか監視しながらループしている) と呼ぶことがある。

while文は、上記のような回数の指定が行われていない繰り返しの作成に向いている。
例えば、次のようなループは繰り返す回数が特定しにくい。

while文でfor文を表すことができるし、逆にfor文でwhile文を表すことができる。
forとwhileの特徴を把握してうまく使い分けるとループの条件が明確になり、他人にも自分にも理解しやすいプログラムとなる。

for 文を用いるべき場面

例えば、「10000円を利率5%の複利で預金した際の、10年後の預金額を計算するプログラム」を書く(数列の一般項を求めずに、毎年の預金額を求めて計算する)場合、次のように考えればよい。

  1. 預金額が毎年5%ずつ増加する -> (預金額が毎年1.05倍になる)
  2. 10年間繰り返す -> {(預金額が毎年1.05倍になる)を10回繰り返す}

上記の例では繰り返す回数が10回と指定できたので、for文を用いてみる。

// 元本
double amount = 10000;
// 10回繰り返す (0年後から始めて; 10年後まで; 1年ずつ考える)
for (int year = 0; year < 10; year++) {
    amount *= 1.05;
}
System.out.println("10年後は" + amount + "円");

これを実行すると、以下のように表示された。

10年後は16288.946267774418円

小数点以下も表示されているが、今回は気にしないことにする。

while 文を用いるべき場面

先ほどの問題に対して、「10000円を利率5%の複利で預金した際に、預金額が20000円を超えるまでの年数を計算するプログラム」を考える。

  1. 預金額が毎年5%ずつ増加する -> (預金額が毎年1.05倍になる)
  2. 20000円を超えるまで繰り返す -> {(預金額が毎年1.05倍になる)を預金額が20000円を超えるまで繰り返す}

上記の例では繰り返す回数が指定されていないので、while文を用いてみる。

// 元本
double amount = 10000;
// 開始した時点では0年後
int year = 0;
// 20000円を超えるまで繰り返す (= 20000円以下のうちは繰り返す)
// → 20000円以下であることを監視する番兵を置く
while (amount <= 20000) {
    // 1年後に利子をつける
    year++;
    amount *= 1.05;
}
// whileを抜けた = 番兵から逃れた = 20000円を超えた
System.out.println(year + "年後に20000円を超える");

これを実行すると、以下のように表示された。

15年後に20000円を超える

まとめると、for文の場合は「10年後」が、while文の場合は「預金額が20000円を超えるまで」が強調されているのが読み取れるだろうか。

do-while 文

「"条件BがtrueであったらDを実行する"ことを繰り返す」ような文は while 文で以下のように表せた。

while ( B ) {
    D
}

  
Javaではfor文やwhile文のほかに、繰り返しを表す構文として do-while文 というものも存在する。

do {
    D
} while ( B ); 

これは「Dを実行してから条件Bがtrueなら繰り返す」ような文である。
したがって、通常のwhile文ではDが1回も実行されない場合があり得るが、後者では少なくとも1回はDが実行される。

do-whileを使用する場面はあまりない。while文を使用する場面で、
さらに必ず計算を1回は行うような繰り返しに遭遇した場合はこの構文を用いればよい。

などでdo-whileを見かけることはある。

複雑な条件判定

かつ (and also)

Javaでは「aの値が0以上10未満」という条件に対して、以下のような条件式を書くことはできない。

0 <= a < 10

このようなときは、次のように分割して考える。

「aの値が0以上 かつ aの値が10未満」

このように、2つの条件のうち両方を満たす際にだけ結果が true となるような演算子として && という演算子がある。

0 <= a && a < 10
if (0 <= a && a < 10) {
    System.out.println("aの値が0以上10未満");
}

上記の条件式は、(0 <= a) と (a < 10) のどちらも true であった場合のみ、全体の結果が true となる (実際に数直線を書いて確認せよ)。

3つ以上の条件式を繋げることもできる。

0 <= a && a < 10 && a != 5

この場合も、aの値が0以上10未満でかつ5以外 であったときのみ全体の結果が true となる。

または (or else)

A && B は AとBのどちらも true のときのみ全体が true になるが、
AとBのどちらかが true のとき全体が trueになるような演算子として || も存在する。

a < 0 || 10 <= a
if (a < 0 || 10 <= a) {
    System.out.println("aの値が0未満 または 10以上");
}

上記の条件式は、(a < 0) または (10 <= a) のどちらかが true であった場合に、全体の結果が true となる (実際に数直線を書いて確認せよ)。

&&と同様に、3つ以上の条件式を繋げることもできる。

a < 0 || 10 <= a || a == 5

この場合は、aの値が0未満 または 10以上 または 5 のときに全体の結果が true となる。

&&と||を混在させた式も作れる。

0 <= a && a < 10 || 20 <= a && a < 30

比較演算子には優先度があり、||よりも&&の方が優先度が高い。
この場合、&&の方が先に計算されるため、以下のように解釈される。

(0 <= a && a < 10) || (20 <= a && a < 30)

つまり、「aが0以上10未満 または 20以上30未満」と解釈できる。

否定 (not)

0 <= a && a < 10

は、「aの値が0以上かつaの値が10未満」のときに true となる。
ここに、否定を表す ! 演算子をつけると、真と偽が逆転する。

!(0 <= a && a < 10)

これは、「(aの値が0以上かつaの値が10未満) ではない」ときに true となる。

ここで注意すべきことは、次の文は !(0 <= a && a < 10) を 表していない

「aの値が 0以上ではない かつ 10未満ではない」

次の2つを数直線上に書き表してみよ。

否定を表す演算子 ! は他の二項演算(||, &&, +, *, .. など)より優先度が高いので、
否定を取りたい場合は対象の条件式全体を()で括った後に ! をつけると間違いがない。

例えば、以下の式を考える。

!a < 0

これは、以下のように解釈される。

(!a) < 0

Javaでは、「(aではない)は0未満」という式は許されていないため、コンパイルエラーとなる。
以下のようにしてやればよい。

!(a < 0)

これは、「aは0未満ではない (= aは0以上)」と解釈される。

演算子のまとめ

ここまでにでてきた演算子についてまとめた。
優先順位が高いものほど先に計算され、同じ優先順位を持つものは結合方向に従って先に計算される。

「左から」とある演算子 + を例に挙げると

a + b + c + d


((a + b) + c) + d

と解釈される。

優先順位 項数 結合方向 演算子 意味
13 単項 なし ++ 変数の中身を1だけ増やす
13 単項 なし -- 変数の中身を1だけ減らす
13 単項 右方向から ! 論理否定(true->false, false->true)
12 二項 左から * 乗算
12 二項 左から / 除算
12 二項 左から % 剰余
11 二項 左から + 加算
11 二項 左から - 減算
9 二項 なし > より大きい
9 二項 なし < より小さい
9 二項 なし >= 以上
9 二項 なし <= 以下
8 二項 なし == 等しい
8 二項 なし != 等しくない
4 二項 左から && かつ(and also)
3 二項 左から || または(or else)
1 二項 右から = 代入

他にも以下のような演算子がある。興味があれば調べてみること。

優先順位 項数 結合方向 演算子 意味
13 単項 右方向から ~ ビット反転(0x00000000->0xffffffff)
10 二項 左から << 左シフト
10 二項 左から >> 算術右シフト
10 二項 左から >>> 論理右シフト
7 二項 左から & ビット論理積(and)
6 二項 左から ^ ビット排他的論理和(xor)
5 二項 左から | ビット論理和(or)
2 三項 なし ? : ?より左がtrueなら?の右を実行、falseなら:の右を実行