目次
検証環境と備考
検証環境:IntelliJ IDEAのKotlin Notebook
備考1:Kotlinの高階関数の使い方を一通りまとめたものになります(高階関数ではないものもああります)。用語や分類に関しては正しくない可能性があります。
備考2:printlnを省略し変数の横に出力結果を表示している場合があります。
ラムダ式
ラムダ式は、名前を持たない関数(関数リテラル)です。変数に代入したり、関数の引数として直接渡したりできます。
基本構文
{ 引数1: 型, 引数2: 型 -> 処理 } の形式で記述します。
|
1 2 |
val sum = { a: Int, b: Int -> a + b } println(sum(10, 20)) // 30 |
暗黙の引数
引数が1つだけの場合、引数の定義を省略して it という名前で参照できます。
|
1 2 |
val square: (Int) -> Int = { it * it } println(square(5)) // 25 |
トレーリングラムダ (Trailing Lambda)
関数の最後の引数が関数型である場合、ラムダ式を関数のカッコ () の外側に記述できます。
|
1 2 3 4 5 6 7 8 |
fun repeatAction(times: Int, action: () -> Unit) { for (i in 1..times) action() } // カッコの外に書ける repeatAction(3) { println("Hello!") } |
関数参照(::)
既存の関数やプロパティを、関数型の値として扱うための記法です。
基本構文
::関数名 で参照します。
|
1 2 3 4 5 |
fun isEven(n: Int) = n % 2 == 0 val numbers = listOf(1, 2, 3, 4) // 関数参照を渡す val evens = numbers.filter(::isEven) |
プロパティ参照
プロパティ名に :: を付けることで、そのプロパティ自体(メタデータやゲッター)を参照できます。
|
1 2 3 |
val name = "Kotlin" val lengthReference = String::length println(lengthReference.get(name)) // 6 |
クラスメソッドの参照
特定のクラスのメソッドを参照することも可能です。
|
1 2 |
val isNotEmpty = String::isNotEmpty println(isNotEmpty("Hello")) // true |
コレクション操作関数
map
各要素を変換して、新しいリストを返します。
|
1 2 |
val numbers = listOf(1, 2, 3) val doubled = numbers.map { it * 2 } // [2, 4, 6] |
filter
条件に合う要素だけを抽出します。
|
1 2 |
val numbers = listOf(1, 2, 3, 4) val evens = numbers.filter { it % 2 == 0 } // [2, 4] |
fold (または reduce)
全要素を1つの値にまとめあげます。
|
1 2 |
val numbers = listOf(1, 2, 3, 4) val sum = numbers.fold(0) { acc, i -> acc + i } // 10 |
flatMap
1つの要素から複数の要素を生成し、平坦化します。
|
1 2 |
val lists = listOf(listOf(1, 2), listOf(3, 4)) val flattened = lists.flatMap { it } // [1, 2, 3, 4] |
associateBy
リストをMap(キー・値)に変換します。
|
1 2 3 |
data class User(val id: String, val name: String) val users = listOf(User("1", "Alice"), User("2", "Bob")) val userMap = users.associateBy { it.id } // {1=User(id=1, name=Alice), 2=User(id=2, name=Bob)} |
groupBy
指定したキーで要素をグループ分けします。
|
1 2 |
val words = listOf("apple", "banana", "apricot") val grouped = words.groupBy { it.first() } // {a=[apple, apricot], b=[banana]} |
partition
条件に「合うもの」と「合わないもの」にリストを2分割します。
|
1 2 3 |
val numbers = listOf(1, 2, 3, 4) val (evens, odds) = numbers.partition { it % 2 == 0 } println("$evens, $odds") // [2, 4], [1, 3] |
distinctBy
特定のプロパティに基づいて重複を除去します。
|
1 2 |
val users = listOf(User("1", "Alice"), User("1", "Alice Duplicate")) val distinctUsers = users.distinctBy { it.id } // [User(id=1, name=Alice)] |
zip
2つのリストを組み合わせて、ペアのリストを作ります。
|
1 2 3 |
val names = listOf("Alice", "Bob") val scores = listOf(100, 80) val paired = names.zip(scores) // [(Alice, 100), (Bob, 80)] |
sumOf
要素の特定のプロパティ(価格など)の合計を算出します。
|
1 2 3 |
data class Product(val name: String, val price: Int) val products = listOf(Product("A", 100), Product("B", 200)) val total = products.sumOf { it.price } // 300 |
maxByOrNull
最大のプロパティを持つ要素を取得します(空ならnull)。
|
1 2 3 |
data class Product(val name: String, val price: Int) val products = listOf(Product("A", 100), Product("B", 200)) val winner = products.maxByOrNull { it.price } // Product(name=B, price=200) |
sortedWith
compareBy と組み合わせて、複数の条件でソートします。
|
1 2 3 |
data class Product(val name: String, val price: Int) val products = listOf(Product("A", 100), Product("B", 200), Product("C", 150)) val sorted = products.sortedWith(compareBy({ it.price }, { it.name })) // [Product(name=A, price=100), Product(name=C, price=150), Product(name=B, price=200)] |
スライシング・チャンキング
windowed
指定したサイズでスライドさせます。
|
1 2 3 4 5 |
val numbers = (1..5).toList() // 要素3つのウィンドウを1つずつずらす val windows = numbers.windowed(3) // [[1, 2, 3], [2, 3, 4], [3, 4, 5]] // ラムダ式で各ウィンドウの平均を取る例 val averages = numbers.windowed(3) { it.average() } // [2.0, 3.0, 4.0] |
chunked
指定サイズで分割します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
val numbers = (1..7).toList() // 3つずつの塊に分ける(最後は余り) val chunks = numbers.chunked(3) // [[1, 2, 3], [4, 5, 6], [7]] // 最後の余りを埋めてサイズを揃える(パディング) val paddedChunks = numbers.chunked(3) { if (it.size < 3) it.toList() + List(3 - it.size) { 0 } else it.toList() } // [[1, 2, 3], [4, 5, 6], [7, 0, 0]] // この書き方はダメ 渡される変数は一時的なものになります。 // この一時的な変数と切り離すために it.toList()のようにして、 // 新しいインスタンスを生成するように気をつける必要があります val strangeChunks = numbers.chunked(3) { it } // [[7], [7], [7]] |
zipWithNext
前後ペアを作る関数になります。
|
1 2 3 4 5 |
val numbers = listOf(1, 2, 4, 7) // 隣接する要素とのペアを作る val pairs = numbers.zipWithNext() // [(1, 2), (2, 4), (4, 7)] // ラムダ式で差分を計算する例 val diffs = numbers.zipWithNext { a, b -> b - a } // [1, 2, 3] |
runningFold / runningReduce
途中経過を保持しリストを返します。
|
1 2 3 4 5 |
val numbers = listOf(1, 2, 3, 4) // foldの途中経過(累積値)をリストで返す val cumSum = numbers.runningFold(0) { acc, i -> acc + i } // [0, 1, 3, 6, 10] // reduce版(初期値なし) val intermediateDiffs = numbers.runningReduce { acc, i -> acc + i } // [1, 3, 6, 10] |
遅延評価
asSequence()
大量データを扱う際、中間リストを作成せずにパイプライン処理を行います。
|
1 2 3 4 5 6 |
val largeList = (1..1000000).toList() val result = largeList.asSequence() .filter { it % 2 == 0 } .map { it * it } .take(10) .toList() // 終端操作で初めて実行される |
カリー化・部分適用
カリー化
カリー化は、複数の引数を取る関数を、1つの引数を取る関数のチェーンに変換することです。
例えば $f(x, y, z)$ という関数を $f(x)(y)(z)$ という形に分解するプロセスを指します。
引数が増えると宣言する関数のシグネチャが長くなっていきますが、うまく利用するとBuilderパターンのようなチェーンができます。しかし、通常の実装ではカリー化を利用した実装よりデータクラスを利用した実装が現実的になる可能性があります。
|
1 2 3 4 5 6 7 8 |
// 普通の関数 val add: (Int, Int) -> Int = { a, b -> a + b } // カリー化された関数 val curriedAdd: (Int) -> (Int) -> Int = { a -> { b -> a + b } } // 呼び出し方 val result = curriedAdd(5)(10) // 15 |
部分適用
部分適用は、元の関数の引数リストのうち、一部にだけ値を放り込んで、残りの引数を後で受け取る状態にすることです。関数の特定の引数を固定化するために利用します。カリー化された関数を利用すると部分適用が柔軟にできます。
|
1 2 3 4 5 6 7 8 |
//カリー化された関数 fun add(a: Int): (Int) -> Int = { b -> a + b } //カリー化された関数に部分適用を行う val addFive = add(5) //呼び出し方 println(addFive(10)) // 15 |
スコープ関数
let
nullチェックと組み合わせて、値を安全に別の形へ変換する際に多用します。
|
1 2 3 4 5 |
val name: String? = "Kotlin" val length = name?.let { println("Processing $it") it.length } |
run
オブジェクトのプロパティを使いつつ、計算結果を返します。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
class Service() { var port = 3000 fun query() : String { return "port: $port" } } val service = Service() val result = service.run { port = 8080 query() // 戻り値 } println(result) // port: 8080 |
also
値を書き換えずに、ログ出力などを差し込むのに適しています。
|
1 2 3 |
val user = User("1", "Alice").also { println("Created user: $it") } |
apply
オブジェクトの初期化設定を行い、そのオブジェクト自身を返します。例ではデータクラスを利用していますが、これはあまりよい例ではありません。データクラスを使用するなら変数はvalにし不変オブジェクトを用いて、copyメソッドの利用を検討すべきです。
|
1 2 3 4 5 6 |
// 宣言をvarにしています data class User(var id: String, var name: String) val user = User("1", "Alice").apply { name = "Alice Revised" } println(user) // User(id=1, name=Alice Revised) |
with
特定のオブジェクトに対して複数の操作をまとめて行いたい場合に使います。
|
1 2 3 4 5 6 7 8 9 |
data class User(val id: String, val name: String) val user = User("1", "Alice") with(user) { println("ID: $id") println("Name: $name") // 最後に記述した内容が戻り値になる } // ID: 1 // Name: Alice |
チェック関数
takeIf / takeUnless
条件を満たした時だけその値を返し、そうでなければ null を返します。
takeIfはAND条件をtakeUnlessはOR条件を作り出すことができます。ただし、まずは通常のif文やif式の利用を検討したほうが良いでしょう。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
val input = "kotlin" val result = input.takeIf { it.startsWith("k") } // "kotlin" val absent = input.takeUnless { it.isEmpty() } // "kotlin" val andCondition = input.takeIf{ it.startsWith("k") } ?.takeIf { it.isNotEmpty() } ?.let { "True $it" } ?: "False or Default Value" println(andCondition) // True kotlin val orCondition = input.takeUnless { it.startsWith("k") } ?.takeUnless { it.startsWith('j') } ?.let { "False" } ?: input.let{"True $it"} println(orCondition) // True kotlin |
ifEmpty
コレクションが空の場合にデフォルト値を返します。
|
1 2 |
val list = emptyList<String>() val default = list.ifEmpty { listOf("default") } |
ループ
repeat(n) { ... }
指定回数処理を繰り返します。
|
1 |
repeat(3) { println("Hello") } |
表明(Assertion)
require(condition) / check(condition)
前提条件のガードをします。
|
1 2 3 4 5 6 7 |
fun setAge(age: Int, isInitialized: Boolean) { require(age >= 0) { "Age must be positive" } // IllegalArgumentException check(isInitialized) { "Not initialized" } // IllegalStateException } //setAge(-1,false) // IllegalArgumentException //setAge(1,false) // IllegalStateException setAge(1, true) |
インデックス付きのコレクション操作関数
mapIndexed
要素とそのインデックスをセットで変換します。
|
1 2 |
val indexed = listOf("A", "B").mapIndexed { index, value -> "$index: $value" } println(indexed) // [0: A, 1: B] |
forEachIndexed
インデックスを使って副作用を実行します。
|
1 2 3 |
listOf("A", "B").forEachIndexed { index, value -> println("$index: $value") } // 0: A // 1: B |
filterIndexed
インデックスを条件に抽出します。
|
1 2 |
val filtered = listOf("A", "B", "C").filterIndexed { index, _ -> index % 2 == 0 } println(filtered) // [A, C] |
indices / withIndex()
インデックス範囲の取得や分割代入。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
val list = listOf("A", "B") list.indices.forEach { index -> println("$index") } // 別の書き方 for (index in list.indices) { println("$index") } // 0 // 1 list.withIndex().forEach { (index, value) -> println("$index: $value") } // 別の書き方 for ((index, value) in list.withIndex()) { println("$index: $value") } // 0: A // 1: B |
indexOfFirst / indexOfLast
条件に一致する要素のインデックスを返します。
|
1 |
val firstA = listOf("A", "B", "A").indexOfFirst { it == "A" } // 0 |
foldIndexed
累積計算にインデックスの情報を加味できます。
|
1 2 3 4 5 6 |
val sum = (1..4).foldIndexed(0) { index, acc, b -> acc + (index * b) } // 0*1 + 1*2 + 2*3 + 3*4 println(sum) // 20 |
エラーハンドリング
runCatching
成功(Success)か失敗(Failure)をカプセル化した Result 型を返します。
|
1 2 3 4 5 6 7 8 9 |
val result = runCatching { throw Exception("error") "123".toInt() }.onSuccess { println("Success: $it") }.onFailure { println("Error: ${it.message}") } // Error: error |