Java : 拡張for文 (for-eachループ文) の使い方
拡張for文(for-eachループ文) は、単純なコーディングミスを減らすことができる強力な文法です。
この記事では、通常の for文と比較しつつ、拡張for文の基本的な使い方についてご紹介します。
※本記事は、通常のfor文は理解しているかたを対象としています。
拡張for文
コード例
さっそく「基本for文」と「拡張for文」のコード例を見てみましょう。
それぞれの for文で、配列の全ての要素を出力させます。
final String[] strings = {"aaa", "bbb", "ccc"};
// ----------------
// 基本for文
for (int i = 0; i < strings.length; i++) {
System.out.println(strings[i]);
}
// 結果
// ↓
//aaa
//bbb
//ccc
// ----------------
// 拡張for文
for (final String s : strings) {
System.out.println(s);
}
// 結果
// ↓
//aaa
//bbb
//ccc
拡張for文のほうが、少しシンプルに記述できていますね。
構文
上記の引用は、公式の Java言語仕様による拡張for文の構文です。
簡単に書くと
for (T value : "配列" か "Iterable" を実装したクラス) {
...
}
となります。
拡張for文で使えるオブジェクトは、配列 か Iterable を実装したクラス(またはインタフェース) です。
Iterable を実装した代表的なクラス(インタフェース) は List ですね。
T は要素の型で value は変数です。
value には、繰り返される要素が先頭から順番に代入されていきます。
以下は、List を使った例になります。
final List<String> list = List.of("aaa", "bbb", "ccc");
for (final String s : list) {
System.out.println(s);
}
// 結果
// ↓
//aaa
//bbb
//ccc
メリット
基本for文と拡張for文の大きな違いは、条件式 があるかないかです。
例えば、下記のコード例では…
final int[] values = {10, 20, 30, 40, 50};
// 基本for文
for (int i = 0; i < values.length; i++) {
...
}
条件式は "i < values.length" の部分となります。
for (int i = 0; i < values.length; i++) {
^^^^^^^^^^^^^^^^^ <--- この部分!
...
}
条件式(if文 や 基本for文、while文 など) の多いプログラムは、複雑になりがちです。
分岐も多いのでテストも大変になり、不具合も多くなる … と経験的には感じています。
拡張for文は条件式がないので、次のようなメリットがあります。
- 単純にコーディングミスが減ります。
- for文の意図 … 全ての要素を順番に処理する ということが明確になります。
実感してもらうために、基本for文の例を5つ出してみます。
- values 配列の全ての要素を表示させる
というのを正解とします。
何番が正しくて、何番が間違いか、ぱっと見てすぐに分かりますでしょうか。
final int[] values = {10, 20, 30, 40, 50};
// 1番
for (int i = 0; i < 5; i++) {
System.out.println(values[i]);
}
// 2番
for (int i = 0; i <= 5; i++) {
System.out.println(values[i]);
}
// 3番
for (int i = 0; i < values.length; i++) {
System.out.println(values[i]);
}
// 4番
for (int i = 0; i < values.length - 1; i++) {
System.out.println(values[i]);
}
// 5番
for (int i = 0; i <= values.length - 1; i++) {
System.out.println(values[i]);
}
正解は、1番と3番と5番でした。
final int[] values = {10, 20, 30, 40, 50};
// 1番
for (int i = 0; i < 5; i++) {
System.out.println(values[i]);
}
// 結果
// ↓
//10
//20
//30
//40
//50
// 2番
try {
for (int i = 0; i <= 5; i++) {
System.out.println(values[i]);
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(e);
}
// 結果
// ↓
//10
//20
//30
//40
//50
//java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
// 3番
for (int i = 0; i < values.length; i++) {
System.out.println(values[i]);
}
// 結果
// ↓
//10
//20
//30
//40
//50
// 4番
for (int i = 0; i < values.length - 1; i++) {
System.out.println(values[i]);
}
// 結果
// ↓
//10
//20
//30
//40
// 5番
for (int i = 0; i <= values.length - 1; i++) {
System.out.println(values[i]);
}
// 結果
// ↓
//10
//20
//30
//40
//50
ぱっと見て、というのはなかなか難しいかもしれません。
コードが正しいか確認するために、特に 条件式 の部分を注意深く見たのではないでしょうか。
2番は例外が発生するので 間違い だというのは明確です。
ただ、4番はコーディングミスなのか、あえて最後の要素を省いたのか … というのがコードからは伝わりにくいですね。
あとは、人の好みによって、条件式に "<" を使うか "<=" を使うか別れそうです。
final int[] values = {10, 20, 30, 40, 50};
// どちらも正解です。
for (int i = 0; i < values.length; i++) {
System.out.println(values[i]);
}
// 結果
// ↓
//10
//20
//30
//40
//50
for (int i = 0; i <= values.length - 1; i++) {
System.out.println(values[i]);
}
// 結果
// ↓
//10
//20
//30
//40
//50
そんなあれこれも、拡張for文を使うと条件式もなくなり、すっきりしますね。
final int[] values = {10, 20, 30, 40, 50};
// 拡張for文!
for (int v : values) {
System.out.println(v);
}
// 結果
// ↓
//10
//20
//30
//40
//50
自分で定義したクラスを拡張for文で使うには
独自のクラスでも、Iterable インタフェースを実装すれば拡張for文で使うことができます。
Sample クラスを例に見てみましょう。
Iterable<String> を実装しているのがポイントです。
public class Sample implements Iterable<String> {
private static final String[] VALUES = {"aaa", "bbb", "ccc"};
private int index;
@Override
public Iterator<String> iterator() {
return new Iterator<>() {
@Override
public boolean hasNext() {
return VALUES.length != index;
}
@Override
public String next() {
final var value = VALUES[index];
index++;
return value;
}
};
}
}
それでは、Sample クラスを拡張for文で使ってみましょう。
final var sample = new Sample();
for (final var value : sample) {
System.out.println(value);
}
// 結果
// ↓
//aaa
//bbb
//ccc
無事に、拡張for文で要素にアクセスできましたね。
forEach メソッド
Iterable インタフェースには forEach というメソッドがあります。
拡張for文の代わりに ラムダ式 や メソッド参照 を使って要素にアクセスする、という感じですね。
final var list = List.of("aaa", "bbb", "ccc");
// ラムダ式
list.forEach(value -> {
System.out.println(value);
});
// 結果
// ↓
//aaa
//bbb
//ccc
// メソッド参照
list.forEach(System.out::println);
// 結果
// ↓
//aaa
//bbb
//ccc
さて、拡張for文と forEach メソッド、どちらを使ったほうがよいのでしょうか…?
それは場合によると思います。
forEach メソッドを使うことのメリットは、そこまで大きくありません。(記述がシンプルになるくらい?)
ラムダ式はチェック例外が扱いにくかったりと、使いにくいところもあります。
forEach メソッドは無理してまでは使わなくてもOK。
使えたら使う、くらいで良いのかな … と個人的には思います。
拡張for文が使えない場面
拡張for文は先頭から1つずつ順番に全ての要素を処理することを目的としています。
例えば、2つおきに処理したい、という場面では使えません。
final int[] values = {10, 20, 30, 40, 50};
for (int i = 0; i < values.length; i += 2) {
System.out.println(values[i]);
}
// 結果
// ↓
//10
//30
//50
あとは、すべての要素ではなく、例えば3番目まで処理したい、というのも拡張for文ではやりにくいです。
final int[] values = {10, 20, 30, 40, 50};
// 基本for文
for (int i = 0; i < 3; i++) {
System.out.println(values[i]);
}
// 結果
// ↓
//10
//20
//30
拡張for文で無理やり処理する例です。
final int[] values = {10, 20, 30, 40, 50};
int i = 0;
for (int v : values) {
i++;
if (i > 3) {
break;
}
System.out.println(v);
}
// 結果
// ↓
//10
//20
//30
変数が増え、条件式も増え、これでは本末転倒ですね…
そんなときは、ストリーム API を使えばうまくいきます。
こちらはおすすめです。
final int[] values = {10, 20, 30, 40, 50};
Arrays.stream(values).limit(3).forEach(System.out::println);
// 結果
// ↓
//10
//20
//30
TIPS
連番でループ
IntStream.range を使うと簡単に連番のループが可能なのでご紹介します。
IntStream.range(0, 100).forEach(i -> {
System.out.println(i);
});
// 結果
// ↓
//0
//1
//2
// ... 省略 ...
//98
//99
まとめ
拡張for文が使える場面では、積極的に拡張for文を使っていきましょう。
とはいえ、拡張for文には向かない場面もあります。
そんなときは臨機応変に。(代わりに ストリーム API が使えることもあります)
関連記事
- if文の基本
- while文の基本
- for文の基本
- プリミティブ型 (基本データ型)
- リテラルの表記方法いろいろ
- クラスの必要最低限を学ぶ
- インタフェースの default メソッドとは
- インタフェースの static メソッドの使いどころ
- var (型推論) のガイドライン
- アクセス修飾子の基本
- 配列 (Array) の使い方
- switch文ではなくswitch式を使おう
- try-with-resources文でリソースを自動的に解放
- テキストブロックの基本
- 列挙型 (enum) の基本
- ラムダ式の基本
- レコードクラスの基本 (Record Class)
- メソッド参照の基本
- シールクラスの基本(Sealed Class)
- 無名変数の使い方