Java : 拡張for文 (for-eachループ文)
拡張for文(for-eachループ文)は、単純なコーディングミスを減らすことができる強力な文法です。
この記事では、通常のfor文と比較しつつ、拡張for文についてご紹介していきます。
※本記事は通常のfor文は理解しているかたを対象としています。
拡張for文
コード例
さっそく基本for文と拡張for文のコード例を見てみましょう。
それぞれのfor文でstrings配列の全ての要素を出力させます。
final String[] strings = {"a", "b", "c"};
// 基本for文
for (int i = 0; i < strings.length; i++) {
String s = strings[i];
// "a"
// "b"
// "c"
System.out.println(s);
}
// 拡張for文
for (final String s : strings) {
// "a"
// "b"
// "c"
System.out.println(s);
}
拡張for文のほうが、だいぶシンプルに記述できていますね。
公式Java言語仕様の拡張for文の文法です。
簡単に書くと
for (T value : "配列" か "Iterable" を実装したクラス) {
...
}
となります。
拡張for文で使えるオブジェクトは、配列か Iterable を実装したクラス(またはインタフェース)です。
Iterableを実装した代表的なクラス(インタフェース)はListですね。
Tは要素の型でvalueは変数です。
valueには、繰り返される要素が先頭から順番に代入されていきます。
以下は、Listを使った例になります。
final List<String> list = List.of("a", "b", "c");
for (final String s : list) {
// "a"
// "b"
// "c"
System.out.println(s);
}
メリット
基本for文と拡張for文の大きな違いは、条件式があるかないかです。
final int[] values = {10, 20, 30, 40, 50};
for (int i = 0; i < values.length; i++) {
...
}
条件式は "i < values.length" の部分となります。
条件式(if文やswitch、基本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++) {
// 10
// 20
// 30
// 40
// 50
System.out.println(values[i]);
}
// 2番
for (int i = 0; i <= 5; i++) {
// 10
// 20
// 30
// 40
// 50
// ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
System.out.println(values[i]);
}
// 3番
for (int i = 0; i < values.length; i++) {
// 10
// 20
// 30
// 40
// 50
System.out.println(values[i]);
}
// 4番
for (int i = 0; i < values.length - 1; i++) {
// 10
// 20
// 30
// 40
System.out.println(values[i]);
}
// 5番
for (int i = 0; i <= values.length - 1; i++) {
// 10
// 20
// 30
// 40
// 50
System.out.println(values[i]);
}
ぱっと見て、というのはなかなか難しいかもしれません。
コードが正しいか確認するために、特に条件式の部分を注意深く確認したのではないでしょうか。
2番は例外が発生するので間違いだというのは明確です。
ただ、4番はコーディングミスなのか、あえて最後の要素を省いたのか…というのがコードだけからは伝わりにくいですね。
あとは、人の好みによって、条件式に"<"を使うか"<="を使うか別れそうです。
final int[] values = {10, 20, 30, 40, 50};
// どちらも正解です。
for (int i = 0; i < values.length; i++) {
// 10
// 20
// 30
// 40
// 50
System.out.println(values[i]);
}
for (int i = 0; i <= values.length - 1; i++) {
// 10
// 20
// 30
// 40
// 50
System.out.println(values[i]);
}
そんなあれこれも、拡張for文を使うと条件式もなくなり、すっきりしますね。
final int[] values = {10, 20, 30, 40, 50};
for (final int v : values) {
// 10
// 20
// 30
// 40
// 50
System.out.println(v);
}
自分で定義したクラスを拡張for文で使うには
Iterableインタフェースを実装すれば、拡張for文で使うことができるようになります。
コード例を以下に示します。
public class Sample implements Iterable<String> {
private static final String[] VALUES = {"a", "b", "c", "d", "e"};
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;
}
};
}
}
final var sample = new Sample();
for (final var value : sample) {
// "a"
// "b"
// "c"
// "d"
// "e"
System.out.println(value);
}
forEach メソッド
IterableインタフェースにはforEachというメソッドがあります。
拡張for文の代わりにラムダ式やメソッド参照を使う、という感じですね。
forEachメソッドを使うと、さらにシンプルに記述することができます。
final var list = List.of("a", "b", "c");
// ラムダ式
list.forEach(value -> {
// "a"
// "b"
// "c"
System.out.println(value);
});
// メソッド参照
// "a"
// "b"
// "c"
list.forEach(System.out::println);
拡張for文とforEachメソッド、どちらを使ったほうがよいのか?
それは場合によると思います。
拡張for文よりforEachメソッドを使うことのメリットは、そこまで大きくありません。(記述がシンプルになるくらい?)
逆に、ラムダ式はチェック例外が扱いにくかったりと、使いにくいところもあります。
forEachメソッドは無理してまでは使わなくてもOK。
使えたら使う、くらいで良いかなと個人的には思います。
拡張for文が使えない場面
拡張for文は先頭から1つずつ順番に全ての要素を処理することを目的としています。
例えば、2つおきに処理したい、という場面では使えません。
final int[] values = {10, 20, 30, 40, 50};
// 基本for文
for (int i = 0; i < values.length; i += 2) {
// 10
// 30
// 50
System.out.println(values[i]);
}
あとは、すべての要素ではなく、例えば3番目まで処理したい、というのも拡張for文ではやりにくいです。
final int[] values = {10, 20, 30, 40, 50};
// 基本for文
for (int i = 0; i < 3; i++) {
// 10
// 20
// 30
System.out.println(values[i]);
}
拡張for文で無理やり処理する例です。
final int[] values = {10, 20, 30, 40, 50};
int i = 0;
for (final int v : values) {
i++;
if (i > 3) {
break;
}
// 10
// 20
// 30
System.out.println(v);
}
変数が増え、条件式も増え、これでは本末転倒ですね…
Streamインタフェースを使えばうまくいきます。こちらはおすすめです。
final int[] values = {10, 20, 30, 40, 50};
//10
//20
//30
Arrays.stream(values).limit(3).forEach(System.out::println);
TIPS
連番でループ
IntStream.rangeを使うと簡単に連番のループが可能なのでご紹介します。
IntStream.range(0, 100).forEach(i -> {
// 0
// 1
// 2
// ... 省略
// 98
// 99
System.out.println(i);
});
まとめ
拡張for文が使える場面では、積極的に拡張for文を使っていきましょう。
とはいえ、拡張for文には向かない場面もあります。そのときは臨機応変に。(Streamが使える場面もあります)
関連記事
- API 使用例