Java/基礎/反復処理
他の多くのプログラミング言語同様、Javaにも、処理の流れをコントロールする構文が用意されています。 条件分岐や反復処理の構文です。 このページでは反復処理(ループ)を採り上げます。 さっそくサンプルを見てみましょう。
反復処理の例編集
次のサンプルは、一応Hello worldの日本語版(のつもり)です。 Hello world同様、実用的な意味は全くありませんが、反復処理を使っています。
class JpnHello {
public static void main(String[] args) {
char[] msg = "世界よこんにちは".toCharArray();
for (int i = 0; i < msg.length; i++) {
System.out.println(msg[i]);
}
}
}
上の例はfor文と呼ばれる構文を使った例です(4行目にキーワード「for」があることに注意してください)。 コンパイルして実行してみましょう。
$ javac JpnHello.java $ java JpnHello 世 界 よ こ ん に ち は
一応“縦書き”でメッセージが表示されます(コマンドの行頭に出てくる「$」の文字は、プロンプトを表しています)。同様の表示結果になれば成功です。
今回のサンプルには「世界よこんにちは」という文字が出てきました。 こうした、いわゆる“2バイト文字”を使う際、文字コードの問題が起こる場合もあります。
たとえば、WindowsやMac OS Xなどの環境で、このソースコードを「UTF-8」という文字コードで保存したとします。この場合でも、コンパイルは成功するかもしれません。しかし、実行結果は正しく表示されないと思います。 これは、WindowsやMac OS Xなどの環境下では、ソースコードが「Shift-JIS」という文字コードで書かれているという前提になっているからです(Windowsの場合、厳密には「MS932」と呼ばれている文字コードです)。
ソースコードを規定値以外の文字コードで保存した場合は、使用した文字コードをコンパイル時に指定するようにします。たとえば次の例は「UTF-8」を指定している場合です。
$ javac -encoding UTF-8 JpnHello.java
システムによっては、問題はコンパイルだけとは限りません。 たとえば、Mac OS X 10.4の標準のターミナルはUTF-8をサポートしています。 ですからもちろん日本語の表示も可能なのですが、実際には1つ問題があります。 というのも、Mac OS X環境下のJavaは、デフォルトの文字コードをShift-JISと規定しているからです。 すなわち、JavaプログラムはターミナルにShift-JISで出力しますが、 ターミナルはそれをUTF-8で解釈しようとしてしまうのです。
表示結果を正しくするためには、次のようにしてプログラムを実行します。
$ java -Dfile.encoding=UTF-8 JpnHello
このプログラムは次のように書くこともできます。
class JpnHello {
public static void main(String[] args) {
char[] msg = "世界よこんにちは".toCharArray();
int i = 0;
while (i < msg.length) {
System.out.println(msg[i]);
i++;
}
}
}
こちらはwhile文を使った書き方です。 さらに次のような書き方もできます。
class JpnHello {
public static void main(String[] args) {
char[] msg = "世界よこんにちは".toCharArray();
int i = 0;
do {
System.out.println(msg[i]);
} while (++i < msg.length);
}
}
これはdo-while文と呼ばれます。
またさらには、Java SE 5.0 以降を使っている場合、拡張for文と呼ばれるものをつかって次のような書き方も可能です。 これが最もシンプルな書き方です。
class JpnHello {
public static void main(String[] args) {
for (char c: "世界よこんにちは".toCharArray()) {
System.out.println(c);
}
}
}
反復処理を紹介する導入として、カタログ的にいくつかの書き方を紹介しました(もっとも、この例のように配列要素を取り出したい場合は、通常for文が適していると言えるでしょうけれども)。
以降、詳しく見ていくことにしましょう。
反復条件によく使われる比較演算子編集
それぞれの構文について詳しく説明する前に、共通する要素について説明しておきます。
反復処理では、必ず反復条件が指定されます。 反復処理は、反復条件を満たしている間だけ続き、条件を満たさなくなると処理を抜けます。
反復条件は、しばしば比較演算子というものを使って表現されます。 比較演算子は、数学の記号によく似ています。 反復条件でよく使われる比較演算子には、たとえば次のものがあります。
演算子 | 説明 |
---|---|
> | 数学の > と同様。左辺の値が右辺の値よりも大きい場合に真 |
{{{1}}} | 数学の ≧ と同様。左辺の値が右辺の値よりも大きいか、もしくは等しい場合に真 |
< | 数学の < と同様。>の逆。左辺の値が右辺の値より小さい場合に真 |
{{{1}}} | 数学の ≦ と同様。左辺の値が右辺の値よりも小さいか、もしくは等しい場合に真 |
{{{1}}} | 左辺の値と右辺の値が等しい場合に真 |
{{{1}}} | {{{1}}}の逆。左辺の値と右辺の値が等しくない場合に真 |
for文編集
構文と処理の流れ編集
for文の書式はおおよそ次のとおりです。
for (初期処理; 反復条件; 継続処理) { 繰り返す処理 }
処理の進み方は以下のとおりです。
- 「初期処理」に書かれた処理が実行される
- 「反復条件」に書かれた条件に合っているかどうか判定される
- 条件に合っていた場合:
- 「繰り返す処理」に書かれた処理が1回実行される
- 「継続処理」に書かれた処理が実行される
- 2(「反復条件」の判定)に戻る
- 条件に合っていなかった場合:
- 反復処理を終了する
- 条件に合っていた場合:
なお、繰り返す処理は、単文であればブロック({ })を外すこともできます。 たとえば次のように書きます。
for (int i = 0; i < msg.length; i++) System.out.println(msg[i]);
for文を読み解く編集
しばしばfor文では、ループカウンタと呼ばれる変数が使われます。このページの冒頭で掲げた例では変数iがそれに当たります。
この例では「継続処理」が「i++」となっています。これは、iの値を1増やす処理ですね。 for文が適用されるシーンはさまざまありますが、その最も典型的な適用パターンの1つは、このように1回繰り返すごとにループカウンタの値を変化させ、それにより繰り返される処理のパラメータを変化させるというものです。
もしあなたがプログラミングの初心者なら、反復処理に盛り込まれている、こうした“変化”の様子を読み解ける能力を持つことが、重要な基本課題になります。
たとえば、次のコード片は、7の段の九九を表示する処理です。
for (int n = 1; n <= 9; n++) {
System.out.println("7 x " + n + " = " + 7 * n); // A
}
この反復される処理は、繰り返すたびにどのように変化するでしょうか。一緒に想像力を働かせながら、頭の中でプログラムの動作をトレースしてみることにしましょう。
まず、nの値は最初「1」です(これは反復条件「n <= 9」を満たしています)。ですから、Aで示した文は
System.out.println("7 x 1 = " + 7 * 1);
と同等になります。
2回目はどうでしょう? 2回目ではn++され、nの値は2になります。ですから、Aの文は
System.out.println("7 x 2 = " + 7 * 2);
と同等ということになります。簡単ですね?
以下、反復条件を満たさなくなるまで同様のことが繰り返されるわけですが、ではどのように終了するか、終了の際の動作を思い描くことができますか。 一緒にnが8の状態からトレースしてみましょう。
- nが8の状態でAの文が実行される
- n++される。nは9になる
- {{{1}}}のチェックが行われる。nは9だから結果は真
- nが9の状態でAの文が実行される
- n++される。nは10になる
- {{{1}}}のチェックが行われる。nは10だから結果は偽
- 繰り返しが終了する
処理の流れを思い描くことができましたか。 処理の流れをイメージすることは、慣れない間は少し骨が折れるかもしれません。 しかし、落ち着いて考えれば至極簡単なことです。 じっくりと考え方のパターンを把握するように努めてみてください。
掛け算九九の例編集
for文の学習と言えば、典型的な例が掛け算九九を表示するサンプルです。
class Main {
public static void main(String[] args) {
for (int y = 1; y <= 9; y++) {
for (int x = 1; x <= 9; x++) {
String buf = String.format("%dx%d=%2d ", x, y, x * y);
System.out.print(buf);
}
System.out.println();
}
}
}
もしかすると初めのうちは、for文が入れ子になると分かりづらいかもしれません。 処理の流れを追うことができますか?
これができれば、反復処理の基本はほぼマスターできたと言えるだろうと思います。
拡張for文編集
JDK 5.0以降では拡張for文を使うことができます。 冒頭で掲げた例のうち
class JpnHello {
public static void main(String[] args) {
for (char c : "世界よ今日は".toCharArray()) {
System.out.println(c);
}
}
}
が拡張for文を使った例です。
この例では配列を使っていますので、それに合わせて書式を紹介すると、
for (配列要素の型 変数名: 配列) { 繰り返す処理 }
という形になっています。 「変数名」で示した変数に、「配列」で示した配列の各要素が自動的に代入されるのです。
なお、この例では配列を使って説明していますが、拡張for文が使えるのは配列だけではありません。(少し先取りした内容になりますが)コレクションフレームワークや、もっと一般的に言うとIterable<E>
を実装したクラスが拡張for文で使えます。具体的には、
for (E var1 : /* Iterable<E>を実装したクラスのインスタンス */) {
// ここに処理を書く
}
という構文になります。 拡張for文は、そうしたインスタンスにも配列同様の考え方で適用することができます。
while文編集
構文と処理の流れ編集
while文の書式は次のとおりです。
while (/* 反復条件 */) {
/* 繰り返す処理 */
}
処理の進み方は次のとおりです。
- 「反復条件」に書かれた条件に合っているかどうか判定される
- 条件に合っていた場合:
- 「繰り返す処理」に書かれた処理が1回実行される
- 1(「反復条件」の判定)に戻る
- 条件に合っていなかった場合:
- 反復処理を終了する
- 条件に合っていた場合:
for文同様、最初の反復条件の判定が偽となれば、繰り返す処理は一度も実行されません。
ファイルの読み込みの例編集
while文はfor文よりも機能が少なく、シンプルな構造です。 一般に、ループカウンタが不要なケースで使われることが多いと言えるでしょう。
たとえば、次の例は、引数で指定されたテキストファイルを読み込んで表示するサンプルです。
import java.io.*;
class CatLike {
public static void main(String[] args) throws IOException {
for (String fn : args) {
BufferedReader in = new BufferedReader(new FileReader(fn));
String line = null;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
}
}
}
このようにファイルやデータベースからデータを読み込むような処理は、while文が使われることの多い例と言えるかもしれません。
(line = in.readLine()) != null
のような書き方について
上記のサンプルでは、while文の反復条件が(line = in.readLine()) != null
と書かれています。
これは代入演算と比較演算を同時に行う書き方で、こうした類の処理によく使われるパターンですが、慣れない間は奇異に見えるかもしれません。
なぜこうした書き方が可能なのかというと、代入演算の式は、その式で代入結果の値を返すからです。 念のため、処理の流れを追ってみましょう。
まず括弧内の処理、つまりline = in.readLine()
が実行されます。この式は、変数line
に代入された値を返します。要するに、全体でline
が返されると考えてよいでしょう。
続いて、比較演算が行われます。括弧内をline
に置き換えるとわかりよいでしょう。つまりline != null
という判定が行われることになります。line
がnull
になった場合、行はそれ以上ないというサインなので、ループから抜ける必要があります。
上記のサンプルは、for文を使ってたとえば
import java.io.*;
class CatLike {
public static void main(String[] args) throws IOException {
String line = null;
for (String fn : args) {
for (BufferedReader in = new BufferedReader(new FileReader(fn)); (line = in.readLine()) != null;) {
System.out.println(line);
}
}
}
}
というふうに書くこともできます。 どちらを使うかは、基本的に好みの問題です。