Mapの要素を処理したい!foreach、map、tapEach、foreachElementの使い分け

Scala 3.3.4
最終更新:2023年12月7日

[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください

Map クラスには foreachforeachEntrytapEachmap など、 要素のそれぞれを処理するメソッドがいろいろありますが、 どれを使うべきなのか困ることがありますよね。

この記事では、Scalaの Map クラスにおいて副作用の発生するような処理を行う際に使えるメソッドと、その使い分けについてご紹介します。

使い分け早わかり!

早速ですが、foreachforeachEntrytapEachmapの使い分けは以下のようになります。

副作用のある処理をする場合に:

  1. 処理結果を後続の処理では利用しないとき
    1. 処理にキーと値のタプルは不要なとき
      • foreachEntry を使う
    2. 処理にキーと値のタプルが必要となるとき
      • foreach を使う
  2. 処理結果を後続の処理で使いたいとき
    • map を使う
  3. 処理結果ではなく元々の要素を後続の処理に渡したいとき
    • tapEach を使う

以下のマップを題材に具体的な使い方を見てみましょう。

case class Person(firstName: String, lastName: String, age: Int): val fullName: String = s"$firstName $lastName" val people: Map[Int, Person] = Map( 1 -> Person("Andrea", "Thompson", 23), 2 -> Person("Austin", "May", 17), 3 -> Person("John", "Winston", 18), 4 -> Person("Christina", "Chandler", 20), 5 -> Person("John", "Thompson", 23) )

foreach メソッドの使い方

foreach メソッドは、Map クラスの要素それぞれについて副作用を発生させる際にいちばん基本となるメソッドでした。
過去形なのには理由があります。
後述の foreachEntry メソッドについてを参照してください。

foreach メソッドにおいては、それぞれの要素のキーや値は case キーワードで抽出することになります。

people.foreach { case (_, person) => println(person.fullName) }

出力は以下のとおりです。
以降のサンプルではそれぞれ出力結果は同じになります。

John Thompson Andrea Thompson Austin May John Winston Christina Chandler

case キーワードを使用しない場合にはタプルが渡ってきます。
タプルの形で欲しい場合はこれでOKです。

裏を返せば、キーや値など個別のデータを使用したい場合には、_1_2 などといった形で、タプルの変数から展開する必要があります。
この場合、書いた瞬間はよいのですが、後から見ると何のことやらわかりにくいですね。

一旦変数に格納するなど工夫する必要がありました。

people.foreach { tuple => println(tuple._2.fullName) }

そんなときに便利なのが foreachEntry メソッドです。

foreachEntry メソッドの使い方

Scala 2.13 から、foreachEntry メソッドが追加されました。
これは先程と同じような処理を case キーワードによる抽出無しで実現してくれるメソッドです。
foreachEntry メソッドを使用することにより、わずかではありますがコードの見通しが良くなります。

プログラムを書く上では、要素をタプルで欲しい場合よりもキーや値として分解された状態で使用したい場合のほうが多いため、 今後使用頻度の高くなる、基本のメソッドです。
最初に選択肢として挙がるように心がけつつ、必要に応じて foreach メソッドと使い分けていきましょう。

people.foreachEntry((_, person) => println(person.fullName))

出力結果は上述の foreach メソッドを実行したのと同じになります。

map メソッドの使い方

副作用を発生させつつ、後続の処理に値をそのまま渡したいことがあると思います。
その場合、これまでは map メソッド内で副作用を発生させていました。
また過去形ですね。
つまり tapEach メソッドを参照してください。

map メソッドを使った書き方は以下のようになります。
map メソッドについても foreach メソッドと同様に、キーや値を抽出するには case キーワードを使用する必要があります。
前の処理から渡ってきた要素を、またブロックの末尾で明示的に書くことで次の処理に渡しています。

people.map[Int, Person] { case (k, person) => println(person.fullName.toUpperCase) (k, person) }.foreachEntry((_, person) => println(person.fullName))
JOHN THOMPSON ANDREA THOMPSON AUSTIN MAY JOHN WINSTON CHRISTINA CHANDLER John Thompson Andrea Thompson Austin May John Winston Christina Chandler

case キーワードを使用しない場合はタプルが渡ってくるので、 _1_2 を使って中身のキーや値を取得する必要があります。
タプルが欲しい場合と不要な場合とで臨機応変に使い分けるとよさそうです。

people.map[Int, Person] { tuple => println(tuple._2.fullName.toUpperCase) tuple }.foreachEntry((_, person) => println(person.fullName))

tapEach メソッドの使い方

Scala 2.13 から、tapEach メソッドが追加されました。
これは先程と同じような場面において、後続の処理に明示的に要素を渡すことなく次の処理に要素を渡してくれるメソッドです。
map メソッドの場合と異なり、 tapEach メソッドの末尾には何も渡されていないことがわかります。

people.tapEach { case (_, person) => println(person.fullName.toUpperCase) } .foreachEntry((_, person) => println(person.fullName))

出力結果は上述の map メソッドを実行したのと同じになります。

まとめ

foreachforeachEntrytapEachmapの使い分けは以下のようになります(再掲)。

いずれも副作用のある処理をする場合において、 処理結果を後続の処理では利用せず、かつ処理にキーと値のタプルは不要なときは foreachEntry を使う。
処理にキーと値のタプルが必要となるときは foreach を使う。
処理結果を後続の処理で使いたいときは map を使う。
処理結果ではなく元々の要素を後続の処理に渡したいときは tapEach を使う。

特に、2.13 から入った foreachEntry メソッド、tapEach メソッドをぜひ活用してみてください。

サイト内検索