ラムダの書き方や注意点を学びます。
はじめに
ラムダは、リストの操作等でよく使用されることが多いため、リストに対しての操作を例にとって解説していきます。以下は、リストに格納するPerson
オブジェクトです。
名前、年齢、性別を表すプロパティを持つ、ごく単純なデータクラスとなっています。
data class Person(val name: String, val age: Int, val gender: Gender) { } enum class Gender { MAN, WOMAN, }
リストは以下のようなものを使用します。
特に断りのない場合は以下のリストを操作します。
val people = listOf( Person("Bob", 20, Gender.MAN), Person("Alice", 19, Gender.WOMAN), Person("John", 21, Gender.MAN), Person("Ema", 30, Gender.WOMAN), Person("Brad", 18, Gender.MAN))
ラムダとは
平たく言ってしまうと、他の関数に渡すことのできる関数のことです。
基本の書き方
ラムダ式は、{}
で囲むのが基本の書き方です。
ただし、いろいろと省略記法があるので、順に見ていきましょう。
以下の例では、Personオブジェクトのリストから、年齢が最大であるPersonを取得します。
//一番冗長な書き方。引数にラムダを渡している。 people.maxBy({p: Person -> p.age}) //引数の最後がラムダ式の場合、()の外に出せる people.maxBy() { p: Person -> p.age } //さらに、引数がラムダのみの場合、()は省略できる people.maxBy { p: Person -> p.age } //型が推論されるため、Person型であることを明示しなくとも良い people.maxBy { p -> p.age } //さらに、ラムダで使用できる変数itを使用することで、変数宣言も不要となる people.maxBy { it.age }
いろいろなコレクション操作APIを試す
ラムダの基本的な書き方などを理解したところで、実際にコレクションに対してラムダを利用していろいろな操作を行ってみましょう。
以下では、コードと、その実行結果を記載しています。
filter
フィルターを使用すると、ある条件を満たす要素のみを残す新たなリストを取得できます。
例として、成人している(年齢が20以上)の要素を抜き出してみます。
println(people.filter { it.age >= 20 })
[Person(name=Bob, age=20, gender=MAN), Person(name=John, age=21, gender=MAN), Person(name=Ema, age=30, gender=WOMAN)]
map
map
を使用するとコレクションの要素を別の型に変換したリストを取得できます。
例として、Personオブジェクトを名前(String
)へ変換してみましょう。
println(people.map { it.name })
[Bob, Alice, John, Ema, Brad]
all
all
を使用すると、コレクションの全てが指定された条件を満たすかを判定できます。
以下の例では、コレクションのPersonオブジェクトが全て30歳以上かを判定しています。
println(people.all { it.age >= 30 })
false
any
any
を使用すると、コレクション中にある条件を満たす要素が存在しているかどうかを判定できます。コレクション中に1件でも条件に合致すればtrue
となります。
println(people.any { it.age >= 30 })
true
count
count
を使用すると、コレクション中の要素の中から、条件に合う要素の個数を取得できます。
以下の例では、女性のカウントをしています。
println(people.count {it.gender == Gender.WOMAN})
2
find
find
を使用すると、コレクションの中から、与えられた条件に合致するオブジェクトを取得します。
条件に合致するオブジェクトがない場合は、null
が返却されます。
println(people.find { it.name == "Bob" }) println(people.find { it.name == "Michel" })
Person(name=Bob, age=20, gender=MAN) null
groupBy
groupBy
を使用すると、コレクションをある条件でグループ化したMap
を取得できます。
取得できるMap
の型は、Map<指定したキー, リスト>
となります。
よって、以下の例では、Map<Gender, List<Person>>
が取得できます。
println(people.groupBy { it.gender })
{MAN=[Person(name=Bob, age=20, gender=MAN), Person(name=John, age=21, gender=MAN), Person(name=Brad, age=18, gender=MAN)], WOMAN=[Person(name=Alice, age=19, gender=WOMAN), Person(name=Ema, age=30, gender=WOMAN)]}
flatMap
リストのリストなどを平坦に(flatに)するさいに利用します。 実際の例を見てみるとわかりやすいでしょう。 以下は、90年代のNBAチームから抜粋したチームオブジェクトを使用しています。
//各チームには文字列リストでメンバーの名前を保持している val bulls = Team("bulls", listOf("Michael Jordan", "Scottie Maurice Pippen", "Dennis Keith Rodman")) val jazz = Team("jazz", listOf("Karl Malone", "John Stockton")) val teams = listOf(bulls, jazz) println(teams.flatMap { it.members })
[Michael Jordan, Scottie Maurice Pippen, Dennis Keith Rodman, Karl Malone, John Stockton]
withとapply
with
まずはwithから見ていきましょう。
fun alphabet(): String { val sb = StringBuilder() return with(sb) { //with(sb)とすることで、このブロック内ではsbがレシーバとなる。 for (letter in 'A'..'Z') { append(letter) //appendは変数sbに対して実行される } append("¥nNow I know the alphabet!") toString() //最終的にtoString()した結果がreturnされる } }
with
を使用することで、ラムダ内で使用するオブジェクトを指定できます。
このようにすることで、ラムダないで変数名を何度も書くことなくラムダをすっきりと書くことができるようになります。
apply
apply
も、with
とほぼ同様の動きをします。with
との違いは、apply
は常に引数の値として渡されたオブジェクトを返すことです。
先ほどと同じalphabet関数をapply
を使用してリファクタリングしてみましょう。
fun alphabet() = StringBuilder().apply { for (letter in 'A'..'Z') { append(letter) } append("¥nNow I know the alphabet!") }.toString()
apply
を使用すると、レシーバオブジェクトを直接返却するようになります。
そのため、上記の例ではオブジェクトの返却の前にtoString()
を呼び出しています。
toString()
を呼び出さない場合はStringBuilder
オブジェクトが返却されます。
終わりに
ラムダについて、その書き方や使い方などをみてきました。 コレクションAPIとの親和性が高いので、上手に使えるようになると可読性の向上など、メリットは大きそうですね。
今回は以上となります。それではまた。