カテゴリー
Kotlin言語

ゼロから始めるAndroidアプリ(Kotlin編)Unit3 – 1 第2回 後半:ラムダ式と高階関数

Android Developers > スタートガイド > Kotlin と Android をゼロから学ぶ > Unit3 – ナビゲーションUnit3-1 第2回 後半 の内容をまとめたものです(翻訳ではありません)


Unit3 – 1 第2回 後半

第2回 後半 の内容は以下の通りです

  • ラムダ式(lambda)について
  • 関数型(Function type)について
  • 関数を引数にしたり、関数を返したりする手法(高階関数:higher-order function)について
  • ラムダ式や高階関数(関数の引数,関数の戻り値)を使ってコレクションを操作してみます(filter や sortedWith などを使ってみます)
  • Kotlin コードを書いて実際に試してみたいときは、Webブラウザから Kotlin Playground を利用してください

ラムダ式と高階関数

もう言葉が難しそう…

でも、実際にやることはけっこうシンプルなの

ラムダ式(Lambda)

ラムダ式は Unit2 のチュートリアルでも使ってるの

  • forEach , map , filter のサンプルに登場している書き方です
  • メソッドの引数で名前のない関数(無名関数)を作成していたんですが、これがラムダ式(lambda expression)とか単にラムダ(lambda)とか言われるものです
  • Kotlin では変数やクラスに関数を入れて、引数として関数に渡したり、戻り値を関数にしたりすることができます
  • 関数を、Int や String のような型の一種として変数のように扱うことができます

ここにもまとめてありますね

関数型(Function type)

えっ、関数が変数に代入できちゃう!

  • 関数型(function type , arrow type , exponential)は内容が関数になる型です
  • 関数を代入できる変数やパラメータは関数型です
  • 高階関数(higher-order function)を使うと、関数型のパラメータ(引数)を受け取ったり、関数型の戻り値を返したりできます
  • Kotlin では関数型(function type)が使えます
  • Kotlin では関数型をこんな風に書きます
(Int) -> Int
  • これは Int 型のパラメータ(引数)を1つとって、Int 型の戻り値を1つ返す関数型という意味です
  • パラメータ(引数)が複数ある場合は括弧内にコンマで区切って並べます
  • 矢印(->)の後ろが戻り値の型です
  • たとえば、整数を3倍する関数を、関数型の変数 triple に代入するとこんな風になります
val triple: (Int) -> Int = { a: Int -> a * 3 }
  • イコール(=)の後ろの関数はラムダ式で記述しています
  • ラムダ式では、処理の最後の式の値がラムダ式の戻り値になります

関数型の変数を使ったプログラム

まとめておきますね

  • 関数型の変数を使った簡単な Kotlin プログラムです
  • このプログラムの実行結果は 15 になります
fun main() {
    val triple: (Int) -> Int = { a: Int -> a * 3 }
    println(triple(5))
}

このプログラムは次のように書くこともできます

fun main() {
    val triple: (Int) -> Int = { it * 3 }
    println(triple(5))
}
  • Kotlin では、ラムダ式のパラメータが1つの場合、パラメータ部分を丸ごと省略して構いません
  • 処理部分の記述に省略してしまったパラメータが必要なときは、かわりに it という識別子を使います
  • その他、いろいろなラムダ式のサンプルは こちら からどうぞ

高階関数(Higher-order function)

関数とか関数型の変数とかを受け取ったり、返したりする関数ってことですよね

うん。引数の場所にいきなり関数の中身を書いちゃうのにラムダ式を使うの

  • Kotlin で関数を扱うときに使える強力な手法、高階関数(Higher-order functions)について説明します
  • 関数を渡したり、関数を戻り値としたりできます
  • map , filter , forEach 関数はどれも高階関数です。パラメータ(引数)として関数を受け取って処理しています
  • たとえばこんな感じで使っていました
peopleAges.filter { it.key.length < 4 }

sortedWith() 関数の使いかた

sortedWith() 関数は、どんな基準でソートするのかを関数の形で受け取って処理するの

  • 高階関数の例として sortedWith() 関数を使ってみます
  • 文字列リストを単純にソートしたい場合には、コレクションに sorted() を使えばいいんですが、たとえば文字列の長さでソートしたいとなると sorted() ではできません
  • Kotlin では sortedWith() メソッドにラムダ式を渡すことでこれを実現できます
  • ソートのためには2つずつオブジェクトを比較します。一般的なルールとして、関数から負が返されれば2つのオブジェクトは昇順、0ならば同じ、正ならば降順だったと判定します
  • sortedWith() の処理部分に、2つの文字列の長さを取り出して、それを比較するコードを書きます

まず最初に、名前リストを作って sorted() でソートしてみますね

fun main() {
    val peopleNames = listOf("Fred", "Ann", "Barbara", "Joe")
    println(peopleNames.sorted())
}

この名前リストを、sortedWith() を使って名前の文字数でソートしてみますね

fun main() {
    val peopleNames = listOf("Fred", "Ann", "Barbara", "Joe")
    println(peopleNames.sorted())

    println(peopleNames.sortedWith { str1: String, str2: String -> str1.length - str2.length })
}
  • sortedWith() にラムダ式を渡して名前の文字数でソートしています
  • ラムダ式は同じ型の2つのパラメータをとり、整数(Int)を返しています
  • 実行すると次のように表示されます
[Ann, Barbara, Fred, Joe]
[Ann, Joe, Fred, Barbara]

ソートに使う内部処理関数を引数で受け取っている

  • sortedWith() の引数として渡されたラムダ式は2つの文字列型のパラメータ str1 と str2 を持ち、矢印の後ろの処理を行って値を返します
  • ラムダ式の中の最後の式の値が、ラムダ式の戻り値になります
  • sortedWith() は引数のラムダ式で2つの文字列の比較を繰り返し行い、その結果に応じてソートされたリストを返します
  • sortedWith() はソートで使う比較のための内部処理用の関数を、引数として受け取っていることになります

関数って形で処理の方法を受け取ってるんですね

OnClickListener とOnKeyListener

  • 以前のチュートリアルにあった click listener や key listener ではすでにラムダ式を使っています
  • たとえばこんな感じに使っていました
calculateButton.setOnClickListener{ calculateTip() }

うん、見覚えあるかも

  • Kotlinには SAM (Single-Abstract-Method)変換と呼ばれる短縮した記述方法があります。条件が合えば上の例のように非常に簡潔に記述することが可能です
  • これらの概念は難解ですがそれだけの価値があります。理解するのにちょっと時間がかかるかもしれませんががんばりましょう

key listener の方も見てみますね

costOfServiceEditText.setOnKeyListener { view, keyCode, event -> handleKeyEvent(view, keyCode) }
  • こちらは引数になるメソッド(onKeyメソッド)が3つのパラメータをとり、戻り値は Boolean型です
  • このラムダ式の関数型をちゃんと書くと (View, Int, KeyEvent) -> Boolean です
  • ラムダ式の中の処理で使わない引数は、アンダースコア(_) としてもかまいません(コードがすこし読みやすくなります)

単語リストを表示するプログラムを作ってみる

さいごに簡単なプログラムを作ってみますね

  • 今回説明した、コレクション、ラムダ式、高階関数 を使ってプログラムを作成してみます
  • アルファベットが書かれたボタンを並べて、それをクリックするとそのアルファベットで始まる単語のリストが表示されるようにします

今回はボタンをクリックするかわりにプログラム中でアルファベットを指定しちゃいますね

単語リストを用意します

fun main() {
    val words = listOf("about", "acute", "awesome", "balloon", "best", "brief", "class", "coffee", "creative")
}

今回は B ではじまるものを表示しますね

  • filter を使って、単語リストからBではじまる単語を抽出してコレクションにします
  • filter の引数はラムダ式で記述します。startsWith() を使います
val filteredWords = words.filter { it.startsWith("b", ignoreCase = true) }
println(filteredWords)

実行するとこんな感じです

[balloon, best, brief]

リストの項目をランダムに並べ替えます

リストを並べ替えて…

  • コレクションの項目をランダムに並べ替えたリストを返すには shuffled() を使います
  • チェーンにして適用できます
val filteredWords = words.filter { it.startsWith("b", ignoreCase = true) }
    .shuffled()

実行するとこんな感じです。リストの項目の順番が変わっているのが分かります

[brief, balloon, best]

出力される項目の数を制限します

表示する項目数を絞って…

  • リストで返される単語を一部だけにしたいときは take() を使います
  • たとえば take(2) とすると、リストの最初の2項目だけが返されます
  • チェーンにして適用できます
val filteredWords = words.filter { it.startsWith("b", ignoreCase = true) }
    .shuffled()
    .take(2)

実行するとこんな感じです

[best, balloon]

最後にソートして出力します

最後にソートすると…

  • 最後に2単語だけになった単語リストをソートします。sorted() を使います
  • チェーンにして適用できます
val filteredWords = words.filter { it.startsWith("b", ignoreCase = true) }
    .shuffled()
    .take(2)
    .sorted()

実行するとこんな感じです

[best, brief]

うん、いい感じかも

コードをまとめるとこんな感じになります

fun main() {
    val words = listOf("about", "acute", "awesome", "balloon", "best", "brief", "class", "coffee", "creative")
    val filteredWords = words.filter { it.startsWith("b", ignoreCase = true) }
        .shuffled()
        .take(2)
        .sorted()
    println(filteredWords)
}

まとめ

ちょっと難しかったですね、お疲れさまでした

  • ラムダ式(lambda)は名前のない関数(無名関数)です
  • 高階関数(higher-order function)は、関数を受け取ったり、関数を返したりする関数のことです

参考