Javaプログラミング基礎

講義資料

while文を用いた繰り返しプログラム

繰り返しにはいくつかの書き方がありますが、 今回は while 文をとりあげます。

- while文

while 文の文法的な説明は 教科書 pp.48〜49 のとおりです。

文法的には、while文はfor文よりも単純です。 条件式が成立する限り中身を繰り返し続ける、というだけです。 for文のように初期化、条件、差分といった枠組みがないため 自由度の高い繰り返しを書くことが可能ですが、 変数の意味や値の変化の仕方などの繰り返しの構造を、 よりきちんと理解しておく必要があります。

下のプログラムはwhile文のシンプルな例です。

int i = 0;
while (i < 3) {
    処理
}

変数 i が 3 より小さい間「処理」を繰り返します。 繰り返しの中で、i が変化しなければ、 永久に「処理」を繰り返し繰り返すことになってしまいます。 そこで「処理」の中には、 例えば次のように、 i を変化させるようなプログラムを書くことになります。

int i = 0;
while (i < 3) {
    処理
    i++;
}

上のプログラムで行っていることは、 for文を使うと次のように書くことができます。 for文とwhile文の文法を比較しましょう。

for (int i = 0; i < 3; i++) {
    処理
}

- 例題 1: 1 から n までの合計値を求める

for文の例題でとりあげた、 1からnまでの合計を求めるプログラムをwhile文で書くことを考えてみましょう。 for文を用いると次のように書くことができました。

class SumOneToThree {
    public static void main(String[] args) {
        int sum = 0;

        for (int i = 1; i <= 3; i++) {
            sum = sum + i;
        }
        System.out.println("1 から 3 までの和は " + sum);
    }
}

このプログラムをwhile文を使って書き換えると以下のようになります。 繰り返しの制御に関係する下線部分に注目しましょう。

class SumOneToThree2 {
    public static void main(String[] args) {
        int sum = 0;

        int i = 1;
	while (i <= 3) {
            sum = sum + i;
            i++;
        }
        System.out.println("1 から 3 までの和は " + sum);
    }
}

- 例題 2: 素数かどうか判定するプログラム

for文の回の例題をwhile文を使って書き換えてみましょう。 for文を使ったプログラムは次のようなものでした。

class IsPrime {
    public static void main(String[] args) {
        int n = 31;

        boolean isPrime = true;
        for (int i = 2; i <= n - 1; i++) {
            if (n % i == 0) {
                isPrime = false;
                break;
            }
        }

        if (isPrime)
            System.out.println(n + " is a prime number.");
        else
            System.out.println(n + " is not a prime number.");
    }
}
while文を使って書き換えると次のようになります。
class IsPrime2 {
    public static void main (String[] args) {
        int n = 31;

        boolean isPrime = true;
	int i = 2;
        while (i <= n - 1) {
            if (n % i == 0) {
                isPrime = false;
                break;
            }
            i++;
        }

        if (isPrime)
            System.out.println(n + " is a prime number.");
        else
            System.out.println(n + " is not a prime number.");
    }
}

- 例題 3: 自然数の和

1+2+3+ ... +n の和を計算し、 和が100を越えた時点で終了するプログラムを考えてみましょう。

class SumUntil100 {
    public static void main(String[] args) {
        int sum = 0;

        int i = 1;
	while (sum <= 100) {
            sum = sum + i;
            i++;
        }
        System.out.println("1 から " + (i-1) + " までの和は " + sum);
    }
}

このプログラムのポイントは、 繰り返しを続けるための条件が繰り返しの回数ではなく、 変数 sum の値であるということです。 i は繰り返しを続けるかどうかの判断には関係がなく、 単に 1 刻みに数を数える役割しかありません。

- for文とwhile文の使い分け

例えば、次のプログラムについて考えてみましょう。

int sum = 0;
for (int i = 1; i <= n; i++)
    sum = sum + i;

このプログラムの働きは 1 から n までの和を求めることです。 また、while 文を用いた同じ働きをするプログラムは次のとおりです。

int sum = 0;
int i = 1;
while (i <= n) {
    sum = sum + i;
    i++;
}

2つのプログラムを見比べてみると、 「i を 1 から n まで 1 ずつ増やしながら繰り返す」 という論理を分かりやすく表しているのは for 文の方です。

while 文の方は、i++; が離れた位置にある (この場合はわずか 2 行ですが、 一般にはもっと離れることが多いのです) ので、 繰り返しに関係がある処理がどこにあるかを見分けることが難しくなります。 また、sum = 0;sum = sum + iが繰り返しの中身の仕事であり、 一方、i = 1;i++; が繰り返しの制御に関係している、 ということを一目で見抜くことは難しいと言えます。

このように、for文が適当なケースとは、 あらかじめ繰り返す回数が決まっているような場合であると言えるでしょう。 例えば、 1, 2, 3, ..., 100 の順に値を代入しながら繰り返すといった場合には 適していると言えます。

一方、上の「例題3:自然数の和」でとりあげたような、 繰り返しの中で行う処理の結果によって繰り返すか、 打ち切るかを決めるようなケースでは、while 文の方が適当だと言えます。

さまざまなプログラムの読み書きを経験し、 これらの使い分けのテクニックを身につけるのです。

- 例題 4: 高度な繰り返し: 二重の繰り返しでグラフを描く

y = x2 のグラフを描きます。 ただし、普通のグラフの描き方を 90 度回転したものです。 グラフのようなものはグラフィック表示にすることが望ましいのですが、 今のところ文字による表示法しか学んでいません。 そこで、文字を並べることによってグラフらしきものを表示することにします。

このプログラムのファイル名は Graph.java としています。

for文版

class GraphFor {
    public static void main(String[] args) {
	for (int x = -8; x <= 8; x++) {
	    int y = x * x;
	    for (int i = 0; i < y; i++) 
		System.out.print(" ");
	    System.out.println("*");
	}
    }
}

while文版

class GraphWhile {
    public static void main(String[] args) {
	int x = -8;
	while (x <= 8) {
	    int y = x * x;
	    int i = 0;
	    while (i < y) {
		System.out.print(" ");
		i++;
	    }
	    System.out.println("*");
	    x++;
	}
    }
}

このプログラムは繰り返しが二重になっています。 while 文の中に while 文が、 あるいは for 文の中に for 文が含まれています。

外側の繰り返しでは、 x の値を -8 から 8 まで 1 ずつ増やしながら繰り返します。 x の値が 9 になったら繰り返しを終了します。

内側の繰り返しでは、 iy より小さい間、 i の値を 0 から 1 ずつ増やしていき、 iy と等しくなった時点で終了になります。 例えば y の値が 3 だったとき、 i の値は 0, 1, 2 と増えていき 3 になった時点で終了します。 要するに、この繰り返しの中身を y の値と同じ回数だけ繰り返すということです。

この繰り返しのたびに System.out.print(" ") を実行しているので、 y の値の数と同じ個数の空白を出力することになります。 これによって、繰り返しが終った後に表示している * は、 画面左端からの距離が y の位置に表示されます。 * の表示には System.out.println を用いているため改行されるので、 x を 1 増やしたときの y の値に相当する * は、 次の行に表示されるようになります。

- 場合分け: switch 文

条件を調べて場合分けを行う方法として if 文を学びました。 基本的にはどのような場合分けも if 文を用いれば書くことができます。 しかしながら、 if 文で書くと、 不必要に煩雑になって間違いをしやすくなる場合があります。

次のプログラムは与えられた月に対して、 その月が何日あるかを表示するプログラムです。

class DayOfMonth1 {
    public static void main (String[] args) {
        int n = 3;

        if (n == 1)
            System.out.println(31);
        else if (n == 2)
            System.out.println(28);
        else if (n == 3)
            System.out.println(31);
        else if (n == 4)
            System.out.println(30);
        else if (n == 5)
            System.out.println(31);
        else if (n == 6)
            System.out.println(30);
        else if (n == 7)
            System.out.println(31);
        else if (n == 8)
            System.out.println(31);
        else if (n == 9)
            System.out.println(30);
        else if (n == 10)
            System.out.println(31);
        else if (n == 11)
            System.out.println(30);
        else
            System.out.println(31);
    }
}

この書き方は、理解のしやすさや間違いの可能性を考えると、 あまり良い書き方とは言えません。 例えば、 if 文の一か所を if (m == 3)等と書いてしまったとします。 本来 n と書くべきところを m と書いてしまったというわけです。 人間は変数 nによる場合分けだと思い込むと、 一つだけ間違っていても見過ごしやすく、やっかいな問題になることがあります。

このような場合分けを、分かりやすく書くことができるのが switch 文です。 上のプログラムを switch 文で書くと次のようになります。

class DayOfMonth2 {
    public static void main (String[] args) {
        int n = 3;

        switch (n) {
        case 1:
            System.out.println(31);
            break;
        case 2:
            System.out.println(28);
            break;
        case 3:
            System.out.println(31);
            break;
        case 4:
            System.out.println(30);
            break;
        case 5:
            System.out.println(31);
            break;
        case 6:
            System.out.println(30);
            break;
        case 7:
            System.out.println(31);
            break;
        case 8:
            System.out.println(31);
            break;
        case 9:
            System.out.println(30);
            break;
        case 10:
            System.out.println(31);
            break;
        case 11:
            System.out.println(30);
            break;
        default:
            System.out.println(31);
        }
    }
}

switch 文の中には「 case 定数式: 」という形式が並んでいます。 switch の直後のカッコ内の式がその定数式に等しければ、 その case 文の直後の文から実行します。 case の定数式には、 ==<= などを含む比較式を書くことはできません。

上の例では、例えば n の値が 2 であれば、 case 2:の直後に飛んで、 System.out.println(28); を行います。

また、どの case にも当てはまらない場合は default: 以下を実行します。

case 文に対する実際の処理を書く部分 (例では System.out.println(....); と書いた部分) には、 複数の文を並べて書くことができます。 これは、今まで習った if 文, while文, for 文のように中身に一つの文しか書けず、 複数の文を書くには中カッコ (「{」,「}」) で囲まなければならない、 という原則とは異なっています。

それぞれの case: の処理の後には break 文を書くことが必須です。 この break 文は switch 文からの脱出を意味するものです。 この break 文がないと、ある case 文以下の処理を実行した後、 さらに次の case 文以下の処理まで実行してしまうことになります。 多くの場合これは間違いだと言えます。 case 文以下の処理の最後には break 文を書く、 ということを覚えてしまうのが良いでしょう。

実は、一つの場所に case 定数式: を二つ以上並べて書くこともできます。

class DayOfMonth3 {
    public static void main (String[] args) {
        int n = 3;

        switch (n) {
        case 2:
            System.out.println(28);
            break;
        case 4:
        case 6:
        case 9:
        case 11:
            System.out.println(30);
            break;
        default:
            System.out.println(31);
        }
    }
}

これは、ひと月の日数が 28 日の 2 月と、 30 日の 4, 6, 9, 11 月と、 それ以外の 31 日ある月で場合分けした例です。

switch 文は、実際のプログラムでは登場する機会がそれほど多くはないのですが、 このような多数の場合分けを行う場合に威力を発揮します。