JavaのforEachとは?コレクションをループで処理する2つの方法
今回は、これら2つのループの違いについてみていくことにしましょう。サンプルコードで具体例を取り上げながら、コレクションをループで処理する方法について説明していきます。後半では、ループでMapを扱う方法についても触れるので、ぜひ参考にしてください。
今回は、これら2つのループの違いについてみていくことにしましょう。サンプルコードで具体例を取り上げながら、コレクションをループで処理する方法について説明していきます。後半では、ループでMapを扱う方法についても触れるので、ぜひ参考にしてください。
スキルアップ
2022/12/19 UP
- プログラミング
- Java
- システムエンジニア
Javaでプログラミングをする際、ループを記述する方法は多数あります。なかでも「forEachループ」は、仕組みを理解するのが少し難しい方法かもしれません。また、「for-eachループ」という、名前が似ていて仕組みが異なる構文も存在します。
そこで今回は、これら2つのループの違いについてみていくことにしましょう。サンプルコードで具体例を取り上げながら、コレクションをループで処理する方法について説明していきます。後半では、ループでMapを扱う方法についても触れるので、ぜひ参考にしてください。
なお、Javaにおけるループの基本については、こちらの記事でも解説しています。
【Java入門】whileとdo whileの使い方 - ループ処理の注意点やforとの使い分けを解説
Javaのコレクションと2種類のループ
まずは、コレクションとループの関係について整理しておきましょう。
コレクションとは、データの集まりを表すデータ構造のことです。Javaでは、コレクションでできることはCollectionインターフェースで規定されています。さらに、CollectionはList、Queue、Setに大別されており、これらに準拠したデータ構造はすべてコレクションです。
コレクションに含まれるデータを順番に処理したいときは、ループを用いるのが一般的でしょう。このとき「for each」と呼ばれるループが使われることが多いのですが、それには「forEachループ」と「for-eachループ」の2種類があります。
forEachループ
forEachは、コレクションが備える同名のメソッドを用いたループです。厳密にいえば、forEachメソッドはIterableインターフェースで規定されています。これをコレクションとともに使用できるのは、CollectionインターフェースがIterableインターフェースを継承しているためです。
例えば、プログラム中に「myCollection」という変数名のコレクションがあったとしましょう。その場合、forEachループは次のように記述するのが基本です。
myCollection.forEach(data -> { // ここにループ内の処理を記述 });
メソッドでありながら、ループ内の処理をその場で記述できることがわかるでしょう。ループ変数(ここでは「data」)には、コレクションに格納されたデータが順番に割り当てられます。
for-eachループ
for-eachは、Javaの文法でサポートされているループの構文です。for文やwhile文などの仲間だと考えればよいでしょう。for文を拡張したような記法が採用されていることから、「拡張for文(enhanced for statement)」とも呼ばれています。
変数名が「myCollection」のコレクションがあったとき、for-eachループは次のように記述するのが基本です。
for (var data: myCollection) { // ここにループ内の処理を記述 }
forEachループと同様、コレクション内のデータがループ変数(ここでは「data」)に割り当てられて、順番に処理できるようになっています。
JavaのforEachループを使う
ここからは、ArrayListを題材にしてforEachループの使い方を説明していきます。ArrayListは、Collectionインターフェースの一種であるListインターフェースに準拠したクラスです。そのため、forEachメソッドを備えています。
ArrayListについて詳しく知りたい場合は、こちらの記事もチェックしてみてください。
Javaのリストを使いこなそう!Arraylistの使い方とサンプル
forEachループの具体例
以下のサンプルコードは、forEachループの使い方を示す具体例です。実際にコンパイルして、実行できるプログラムになっています。
サンプルコード:
import java.util.ArrayList; public class Main { public static void main(String[] args) { // コレクションを作成 var myList = new ArrayList<String>(); myList.add("りんご"); myList.add("ぶどう"); myList.add("みかん"); // ループで内容をすべて表示 myList.forEach(value -> { System.out.println("データ:" + value); }); } }
実行結果:
データ:りんご データ:ぶどう データ:みかん
プログラムの前半で、ArrayListを作成してデータを3つ格納しているのがわかるでしょうか。ループを用いれば、これらのデータを1つずつ取り出して処理することができます。
実際にループしているのは、次の部分です。
myList.forEach(value -> { System.out.println("データ:" + value); });
ArrayListのforEachメソッドを呼び出し、ループ変数(ここではvalue)でデータを1つずつ受け取りながら、順番に表示しています。
ここで、ループ変数に型が指定されていないことを疑問に感じた人もいるのではないでしょうか。ループ変数の型はArrayListが格納するデータの型に一致するため、ここでは省略されているのです。次のように型を明示しても、プログラムの意味は変わりません。
myList.forEach((String value) -> { System.out.println("データ:" + value); });
なお、この例のようにループ内の処理が1つ(1行)しかない場合は、次のようなコンパクトな書き方も可能です。
myList.forEach(value -> System.out.println("データ:" + value));
forEachループ内の処理を変数にする
コレクション内のデータを順番に取り出すだけなら、ここまでで説明した知識があれば十分かもしれません。しかし、より複雑な処理も行ないたいと考えているなら、forEachの仕組みについて理解を深めておくことも大切です。
ここでポイントとなるのは、forEachはコレクションがもつメソッドだという点でしょう。これは、ループ内に記述している処理が引数であることを意味しています。forEachループとは、処理内容を引数としてメソッドに渡し、それをメソッドの内部から繰り返し呼び出してもらう仕組みなのです。
ループ内の処理が引数である以上、その内容をメソッドに渡す前に別の変数に格納しておいても構いません。具体的には、次のサンプルコードのようなことが可能です。
サンプルコード:
import java.util.ArrayList; import java.util.function.Consumer; public class Main { public static void main(String[] args) { // コレクションを作成 var myList = new ArrayList<String>(); myList.add("りんご"); myList.add("ぶどう"); myList.add("みかん"); // ループ内の処理を作成 Consumer<String> myLambda = value -> { System.out.println("データ:" + value); }; // ループを実行 myList.forEach(myLambda); } }
処理内容を変数「myLambda」に格納しておき、それを引数としてforEachに渡している様子がわかるでしょう。このような変数や引数は、「ラムダ(lambda)」と呼ばれています。
変数の型に指定しているConsumerは、Javaにおけるラムダの一種です。また、forEachメソッドはConsumer型の引数を受け取ります。
forEachループ内の処理をクラスにする
Javaにおけるラムダは、クラスで実現されています。これは、forEachメソッドに渡す引数の正体は、クラスのインスタンスだということです。
このことを理解するために、ラムダを変数に格納する処理を、より詳細に記述してみましょう。
サンプルコード:
import java.util.ArrayList; import java.util.function.Consumer; public class Main { public static void main(String[] args) { // コレクションを作成 var myList = new ArrayList<String>(); myList.add("りんご"); myList.add("ぶどう"); myList.add("みかん"); // ループ内の処理を作成 var myLambda = new Consumer<String>() { public void accept(String value) { System.out.println("データ:" + value); } }; // ループを実行 myList.forEach(myLambda); } }
Consumerに準拠したクラスをその場で定義し、インスタンス化して変数に格納している様子がわかるでしょうか。ループの処理内容は、クラス内のacceptメソッドに収められました。これは、forEachが内部から呼び出すメソッドです。
ここまでforEachの仕組みがわかれば、ループの処理内容を自前のクラスとしてあらかじめ定義しておくこともできるでしょう。以下は、その具体例です。
サンプルコード:
import java.util.ArrayList; import java.util.function.Consumer; public class Main { static class MyConsumer implements Consumer<String> { public void accept(String value) { System.out.println("データ:" + value); } } public static void main(String[] args) { // コレクションを作成 var myList = new ArrayList<String>(); myList.add("りんご"); myList.add("ぶどう"); myList.add("みかん"); // ループ内の処理を作成 var myLambda = new MyConsumer(); // ループを実行 myList.forEach(myLambda); } }
このように、forEachループはコレクションが備えるメソッドと、そのメソッドに渡す処理内容を定義したクラスによって動作しています。これにラムダの仕組みを組み合わせることによって、コンパクトで読みやすい記述ができるようになっているのです。
forEachループはbreakできない
forEachループでは、for文やwhile文などのようにbreakでループを中断することができません。これは、forEachがメソッドであり、Javaの文法でサポートされた構文ではないためです。
実際にbreakを記述してみれば、エラーとなってコンパイルできないことが確認できるでしょう。
サンプルコード:
import java.util.ArrayList; public class Main { public static void main(String[] args) { // コレクションを作成 var myList = new ArrayList<String>(); myList.add("りんご"); myList.add("ぶどう"); myList.add("みかん"); // ループで内容を表示 myList.forEach(value -> { System.out.println("データ:" + value); if (value == "ぶどう") { break; // コンパイルエラー } }); } }
continueについても、これと同じことがいえます。とはいえ、if文などを用いて工夫すれば、continueと同等の判定はある程度可能です。そのため、実質的にはbreakできないことのほうが問題となるケースが多いでしょう。
どうしてもループ中でbreakしなければならない場合は、次に説明するfor-eachループを使う必要があります。
Javaのfor-eachループを使う
引き続きArrayListを題材にして、ここからはfor-eachループについて説明していきます。
for-eachループの具体例
以下は、for-eachループの使い方を示すサンプルコードです。実際にコンパイルと実行が可能なプログラムになっています。
サンプルコード:
import java.util.ArrayList; public class Main { public static void main(String[] args) { // コレクションを作成 var myList = new ArrayList<String>(); myList.add("りんご"); myList.add("ぶどう"); myList.add("みかん"); // ループで内容をすべて表示 for (var value: myList) { System.out.println("データ:" + value); } } }
実行結果:
データ:りんご データ:ぶどう データ:みかん
プログラム前半のArrayListにデータを格納するところまでは、forEachループのサンプルコードと同様です。
次の部分が、for-eachループにあたります。
for (var value: myList) { System.out.println("データ:" + value); }
ArrayListを対象として、ループ変数(ここではvalue)でデータを受け取りながらループしている様子がわかるでしょう。
このループは、意味合いとしては従来のfor文を用いて次のようにするのと同じです。
for (var i = myList.iterator(); i.hasNext(); ) { var value = i.next(); System.out.println("データ:" + value); }
for文とともに、イテレータ(iterator)を使用しているのがわかるでしょうか。イテレータは、データの集まりに対して反復処理を行なうための仕組みです。これを経由してデータを1つずつ取り出しながら、その値を表示しています。
for-eachループと比べると、従来のfor文とイテレータによるループはやや複雑に見えるでしょう。このように、for-eachループを使うことでループをシンプルに記述できるのです。
for-eachループはbreakできる
for-eachループは、for文やwhile文などと同様にJavaの構文であるという点で、forEachループと異なります。そのため、ループを中断したいときはbreakを使用可能です。
具体的には、次のように記述できます。
サンプルコード:
import java.util.ArrayList; public class Main { public static void main(String[] args) { // コレクションを作成 var myList = new ArrayList<String>(); myList.add("りんご"); myList.add("ぶどう"); myList.add("みかん"); // ループで内容を表示 for (var value: myList) { System.out.println("データ:" + value); if (value == "ぶどう") { break; } } } }
実行結果:
データ:りんご データ:ぶどう
「"ぶどう"までのデータを処理した」という条件で、ループをbreakしている様子がわかるでしょう。このように、for-eachループではif文などと組み合わせて、一定の条件が成立した場合に処理を中断させられます。
なお、continueについてもbreakと同様に使用可能です。
JavaのMapをループで処理するには
データの集まりを表すという点では、MapもArrayListに似たデータ構造です。しかし、JavaのMapとはMapインターフェースを備えたクラスの総称であり、Collectionインターフェースに準拠しているわけではありません。そのため、Mapに格納されたデータをループで処理するには、このあと説明するような少し違った書き方が必要になります。
なお、Mapについてはこちらの記事で解説しているので、併せて参考にしてください。
JavaのMapとは?キーと値を紐付けるデータ構造の特徴と使い方
MapとforEachループ
Mapインターフェースにも、「forEach」という名前のメソッドが規定されています。これを用いて、次のような形式でループを記述可能です。
myMap.forEach((key, value) -> { // ここにループ内の処理を記述 });
一般的なコレクションのforEachループと似ていますが、ループ変数としてキーと値の2つを受け取る点が異なっています。
具体例として、TreeMapをループで処理する方法をサンプルコードで確認してみましょう。TreeMapは、Mapインターフェースに準拠したクラスの一つです。
サンプルコード:
import java.util.TreeMap; public class Main { public static void main(String[] args) { // Mapを作成 var myMap = new TreeMap<String, String>(); myMap.put("apple", "りんご"); myMap.put("grape", "ぶどう"); myMap.put("orange", "みかん"); // キーと値のセットでループ myMap.forEach((key, value) -> { System.out.println("キー:" + key + " / 値:" + value); }); } }
実行結果:
キー:apple / 値:りんご キー:grape / 値:ぶどう キー:orange / 値:みかん
形式に多少の違いはあるものの、ArrayListのforEachと似た方法でループを記述できることがわかるでしょう。
Mapとfor-eachループ
Mapを直接for-eachループで処理することはできません。以下の3つのうち、いずれかのメソッドでコレクションを取得する必要があります。
・keySet():すべてのキーでできたコレクション
・values():すべての値でできたコレクション
・entrySet():キーと値のすべてのペアでできたコレクション
意味合いがforEachループに最も近いのは、entrySet()とfor-eachループの組み合わせです。forEachループのサンプルコードをfor-eachループに置き換えると、以下のようになります。
サンプルコード:
import java.util.TreeMap; public class Main { public static void main(String[] args) { // Mapを作成 var myMap = new TreeMap<String, String>(); myMap.put("apple", "りんご"); myMap.put("grape", "ぶどう"); myMap.put("orange", "みかん"); // キーと値のセットでループ for (var entry: myMap.entrySet()) { var key = entry.getKey(); var value = entry.getValue(); System.out.println("キー:" + key + " / 値:" + value); } } }
実行結果:
キー:apple / 値:りんご キー:grape / 値:ぶどう キー:orange / 値:みかん
forEachループを使った例に比べると少し行数が多くなりましたが、for-eachループでもシンプルに記述できることがわかるでしょう。
JavaのforEachとfor-eachの違いを理解してループを使いこなそう
「forEachループ」と「for-eachループ」は、Javaでループを記述するための方法です。名前が似ていますが前者はメソッド、後者は構文であり、それぞれ仕組みが異なります。
これらの特徴を理解して組み合わせたり使い分けたりすれば、Javaプログラミングの幅も広がるでしょう。今回紹介したサンプルコードも参考にしながら、ぜひ2つのループを活用してみてください。