Kotlinを学び始めると、ほぼ確実に登場するのが高階関数(Higher-Order Function)という概念です。
map や filter、forEach など、普段何気なく使っている関数の多くも、実は高階関数に分類されます。
本記事では、Kotlinの高階関数について
- 基本概念
- 文法と仕組み
- 実務で頻出する使い方
- Kotlin特有の注意点
までを体系的に解説します。
高階関数とは何か
高階関数とは、次のいずれかを満たす関数のことです。
- 関数を引数として受け取る
- 関数を戻り値として返す
Kotlinでは関数が「第一級オブジェクト」として扱えるため、変数に代入したり、引数に渡したり、戻り値として返したりできます。
この性質が、高階関数を自然に扱える理由です。
Kotlinにおける関数型(Function Type)
Kotlinでは、関数の型を次のように表現します。
(Int, Int) -> Int
これは「Int を2つ受け取り、Int を返す関数」を意味します。
関数を変数に代入する例
val add: (Int, Int) -> Int = { a, b -> a + b }
{ a, b -> a + b }はラムダ式addには「関数そのもの」が代入されます
関数を引数として受け取る高階関数
高階関数の最も基本的な形が「関数を引数として受け取る」パターンです。
fun calculate(
a: Int,
b: Int,
operation: (Int, Int) -> Int
): Int {
return operation(a, b)
}
呼び出し側
val result = calculate(10, 5) { x, y -> x + y }
このコードでは、
calculateが関数を引数として受け取る- ラムダ式が
operationに渡される - 実行時に
operation(a, b)が呼び出される
という流れになります。
Kotlinらしい書き方:トレーリングラムダ
Kotlinでは、最後の引数が関数型の場合、ラムダ式を丸括弧の外に書けます。
calculate(10, 5) {
x, y -> x + y
}
この記法により、
- 可読性が高い
- DSLのような書き方が可能
というメリットが生まれます。
標準ライブラリに溢れる高階関数
Kotlinでは、日常的に高階関数を使っています。
map
val list = listOf(1, 2, 3)
val doubled = list.map { it * 2 }
filter
val even = list.filter { it % 2 == 0 }
forEach
list.forEach { println(it) }
これらはすべて「処理内容を関数として受け取る」高階関数です。
関数を戻り値として返す高階関数
高階関数は、関数を生成して返すこともできます。
fun createMultiplier(factor: Int): (Int) -> Int {
return { value -> value * factor }
}
使用例
val triple = createMultiplier(3)
println(triple(10)) // 30
ここでは、戻り値のラムダが factor を保持しています。
クロージャ(Closure)
Kotlinのラムダは、外側の変数をキャプチャできます。
var count = 0
val increment = {
count++
}
このように、
- ラムダは外側の変数にアクセス可能
- 値をコピーするのではなく、変数への参照を保持する
という点が重要です。
特に var の場合、内部的にラップされることがあり、パフォーマンス面の考慮が必要になるケースもあります。
レシーバ付き関数型(Kotlin特有の概念)
Kotlinには レシーバ付き関数型 という独自の仕組みがあります。
String.() -> Int
これは「String をレシーバとして扱い、Int を返す関数」を表します。
通常の関数型との違い
val length1: (String) -> Int = { it.length }
val length2: String.() -> Int = { length }
呼び出し例
println(length1("Hello"))
println(length2("Hello"))
println("Hello".run(length2))
※ String.() -> Int は 拡張関数とは別物であり、変数に代入された場合は invoke 経由で呼び出されます。
DSLを支える高階関数
Kotlin DSLが読みやすい理由は、高階関数 + レシーバ付き関数型 の組み合わせにあります。
fun buildString(block: StringBuilder.() -> Unit): String {
val sb = StringBuilder()
sb.block()
return sb.toString()
}
使用例
val result = buildString {
append("Hello")
append(" ")
append("World")
}
このような構造により、宣言的で直感的なコードが実現します。
inline関数とパフォーマンス
高階関数は便利ですが、ラムダ生成によるオーバーヘッドが発生する場合があります。
inlineの役割
inline fun repeatAction(times: Int, action: () -> Unit) {
for (i in 0 until times) {
action()
}
}
- 呼び出し時に関数本体が展開される
- ラムダオブジェクト生成を抑制できるケースが多い
- ただし、コードサイズ増加とのトレードオフがある
noinline / crossinline
inline fun example(
noinline action1: () -> Unit,
crossinline action2: () -> Unit
) {
// ...
}
noinline:インライン化しないcrossinline:非ローカルリターンを禁止し、別コンテキストでの実行を可能にする
非ローカルリターンに注意
fun test() {
listOf(1, 2, 3).forEach {
if (it == 2) return
}
println("ここには来ない")
}
forEachはinlinereturnにより外側の関数が終了する
意図しない挙動を避けるには、ラベル付き return を使います。
listOf(1, 2, 3).forEach {
if (it == 2) return@forEach
}
println("ここに来る")
Javaとの違い
Javaでは関数を扱うために、
FunctionConsumerPredicate
といった関数型インターフェースを使います。
一方、Kotlinでは
- 関数が自然に型として扱える
- ラムダ構文が簡潔
- DSLや宣言的コードに向いている
という違いがあります。
高階関数が活躍する場面
- コレクション操作
- 処理の共通化・差し替え
- DSL設計
- 非同期処理・コールバック
- 宣言的UIや設定コード
まとめ
Kotlinの高階関数は、
- 再利用性の高い設計
- 可読性の高い宣言的コード
- KotlinらしいDSL表現
を支える、非常に重要な概念です。
単なる文法機能としてではなく、設計の選択肢を広げる道具として理解すると、Kotlinの表現力を最大限に活かせるようになります。
以上、Kotlinの高階関数についてでした。
最後までお読みいただき、ありがとうございました。










