Kotlin: Kotlinの関数とラムダ式
最終更新:2026-08-26
Kotlinでは関数は第一級オブジェクトです――高階関数、ラムダ式、インライン化を活用し、CharlieにOrderProcessorのデータ処理パイプラインを関数型スタイルで構築させたところ、コード量を60%削減できました。
1. 学習内容
- 関数宣言:デフォルト引数、名前付き引数、式本体
- 高階関数:引数や戻り値としての関数
- ラムダ構文:
{ it -> ... }およびitの省略形 - 末尾ラムダ記法の規約:
orders.filter { ... } - インライン関数
inline:ラムダ式の実行時のオーバーヘッドを解消する
2. ある開発者の実体験
(1) 課題:重複注文の処理ロジック
Charlieのチームでは、開発者全員が独自の注文フィルタリング・変換・集計ロジックを記述しており、5つのサービスにまたがって100行以上の重複コードが散在していました。1つのフィルタ条件を変更するには、5カ所を変更する必要がありました。
(2) 高階関数による重複の排除
KOTLIN
// Generic order processor - one function replaces 5 copies
fun <T> processOrders(
orders: List<Order>,
filter: (Order) -> Boolean,
transform: (Order) -> T
): List<T> = orders.filter(filter).map(transform)
// Usage: each call site is 2 lines instead of 20
val highValueIds = processOrders(orders, { it.total > 1_000 }, { it.id })
val vipCustomers = processOrders(orders, { it.total > 10_000 }, { it.customer })
高階関数は「何が変化するか」をパラメータ化するため、「何が変わらないか」は一度だけ記述すれば済みます。
3. 関数の宣言
(1) 基本機能
KOTLIN
// Block body
fun calculateTax(total: Double, rate: Double = 0.08): Double {
return total * rate
}
// Expression body
fun calculateTax(total: Double, rate: Double = 0.08) = total * rate
// Unit return (void)
fun logOrder(order: Order) {
println("Processing ${order.id}")
}
(2) デフォルトパラメータと名前付きパラメータ
KOTLIN
// Default parameters - eliminates method overloading
fun createOrder(
id: String,
total: Double,
status: String = "PENDING",
currency: String = "USD",
taxRate: Double = 0.08
): Order = Order(id, total, status, currency)
// Named arguments - call any parameter by name
val order1 = createOrder("ORD-001", 299.99)
val order2 = createOrder("ORD-002", 1_500.00, taxRate = 0.10)
val order3 = createOrder("ORD-003", 45.50, currency = "EUR", status = "CONFIRMED")
(3) デフォルトパラメータとJavaのメソッドオーバーロード
| 次元 | Javaのオーバーロード | Kotlinのデフォルトパラメータ |
|---|---|---|
| コードサイズ | メソッド数 | 1メソッド |
| 保守性 | 1つのパラメータのN箇所を変更 | 1箇所を変更 |
| 呼び出しの柔軟性 | 位置指定のみ | 名前付き引数、任意の組み合わせ |
| Interop | Javaから直接呼び出し | @JvmOverloads が必要 |
4. 高階関数
高階関数は、関数を引数として受け取ったり、関数を返したりします。これは、Kotlinの関数型プログラミングの要です。
(1) 関数型
KOTLIN
// Function type syntax
val transform: (Order) -> String = { order -> order.id }
val predicate: (Order) -> Boolean = { it.total > 1_000 }
val aggregator: (Double, Order) -> Double = { acc, order -> acc + order.total }
val supplier: () -> Order = { Order("DEFAULT", 0.0, "PENDING") }
// Function as parameter
fun processOrders(orders: List<Order>, transform: (Order) -> String): List<String> {
return orders.map(transform)
}
(2) 戻り値としての関数
KOTLIN
// Return a function
fun getDiscountStrategy(tier: String): (Double) -> Double = when (tier) {
"VIP" -> { total -> total * 0.80 } // 20% off
"GOLD" -> { total -> total * 0.90 } // 10% off
else -> { total -> total } // No discount
}
val discount = getDiscountStrategy("VIP")
val finalPrice = discount(1_000.00) // 800.0
(3) 高階関数 + ラムダ呼び出しチェーン
flowchart LR
A[Orders] --> B[filter]
B --> C[map]
C --> D[sortedBy]
D --> E[fold]
E --> F[Result]
5. ラムダ式
(1) ラムダ構文
KOTLIN
// Full syntax
val total = orders.map({ order: Order -> order.total })
// Type inference
val total = orders.map({ order -> order.total })
// it keyword for single parameter
val total = orders.map({ it.total })
// Trailing lambda convention
val total = orders.map { it.total }
// Multi-parameter lambda
val result = orders.fold(0.0) { acc, order -> acc + order.total }
(2) ラムダ構文の簡略化レベル
| レベル | 構文 | 説明 |
|---|---|---|
| 全文 | { order: Order -> order.total } |
露骨な表現 |
| 簡略版 1 | { order -> order.total } |
型推論 |
| 簡略版 2 | { it.total } |
it との単一パラメータ |
| 末尾のラムダ | orders.map { it.total } |
括弧の外にあるラムダ |
(3) 一般的なラムダパターン
KOTLIN
// let: transform and consume
val formatted = order.total.let { "\$$it USD" }
// also: side effect without changing value
val order = createOrder("ORD-001", 299.99).also {
println("Created order: ${it.id}")
}
// apply: configure object
val order = Order("ORD-001", 0.0, "PENDING").apply {
// Can only be used with var properties
}
// run: execute block and return result
val summary = order.run {
"Order $id: \$${total} USD ($status)"
}
6. 末尾にラムダを置く慣例
関数の最後の引数がラムダ式である場合、それを括弧の外に記述することができます。これがKotlinの最も重要な構文上の簡略化です。
(1) 基本的な使い方
KOTLIN
// Definition
fun processOrders(orders: List<Order>, callback: (Order) -> Unit) { ... }
// Call: trailing lambda
processOrders(orders) { order ->
println(order.id)
}
// If lambda is the ONLY parameter, omit parentheses
orders.forEach { println(it.id) }
(2) DSLの基盤としてのトレーリング・ラムダ
KOTLIN
// This is how DSLs work
fun order(id: String, init: OrderBuilder.() -> Unit): Order {
val builder = OrderBuilder(id)
builder.init()
return builder.build()
}
// Usage: trailing lambda makes it read like configuration
val myOrder = order("ORD-001") {
total(299.99)
status("CONFIRMED")
customer("Alice")
}
7. インライン関数
ラムダ式はJVM上で匿名クラスオブジェクトにコンパイルされ、呼び出しのたびに新しいインスタンスが生成されます。inlineはこのオーバーヘッドを解消します。
(1) インラインの基本
KOTLIN
// Without inline: Lambda creates an object every call
fun process(order: Order, block: (Order) -> String): String {
return block(order)
}
// With inline: Lambda code is inlined at call site
inline fun process(order: Order, block: (Order) -> String): String {
return block(order)
}
(2) インライン効果の比較
KOTLIN
// After inlining, this:
process(order) { it.id }
// Becomes (conceptually):
run { order.id }
(3) inline 対 noinline 対 crossinline
| 修飾子 | 効果 | 使用例 |
|---|---|---|
inline |
呼び出し箇所でインライン化されたラムダ式 | 頻繁に呼び出されるラムダ式 |
noinline |
インライン化を防止(オブジェクトのまま保持) | ラムダ式を格納または渡す必要がある |
crossinline |
インライン化されているが、ローカル外への戻り値を禁止 | 異なる実行コンテキスト内のラムダ |
(4) 実体化された型パラメータ
KOTLIN
// Normal: type is erased at runtime
fun <T> isType(value: Any): Boolean = value is T // ERROR
// reified: inline preserves type info
inline fun <reified T> isType(value: Any): Boolean = value is T // OK
isType<String>("Hello") // true
isType<Int>(42) // true
isType<Int>("42") // false
8. 完全な例:OrderProcessor 機能パイプライン
▶ サンプル:関数型処理パイプライン
KOTLIN
// ============================================
// OrderProcessor - Functional Pipeline
// Feature: Process orders with higher-order functions
// ============================================
data class Order(val id: String, val total: Double, val status: String, val customer: String)
// Generic pipeline processor
inline fun <T, R> List<T>.pipeline(
filter: (T) -> Boolean,
transform: (T) -> R,
crossinline postProcess: (R) -> Unit
): List<R> {
return this.filter(filter).map(transform).also { results ->
results.forEach(postProcess)
}
}
// Discount strategy factory
fun getDiscountStrategy(tier: String): (Double) -> Double = when (tier) {
"VIP" -> { total -> total * 0.80 }
"GOLD" -> { total -> total * 0.90 }
"SILVER" -> { total -> total * 0.95 }
else -> { total -> total }
}
fun main() {
val orders = listOf(
Order("ORD-001", 299.99, "CONFIRMED", "Alice"),
Order("ORD-002", 15_000.00, "CONFIRMED", "Bob"),
Order("ORD-003", 2_500.00, "PENDING", "Charlie"),
Order("ORD-004", 45.50, "CANCELLED", "Alice"),
Order("ORD-005", 8_000.00, "CONFIRMED", "Bob")
)
// Pipeline: filter confirmed -> apply VIP discount -> log
val discount = getDiscountStrategy("VIP")
val processed = orders.pipeline(
filter = { it.status == "CONFIRMED" },
transform = { it.copy(total = discount(it.total)) },
postProcess = { println(" Processed: ${it.id} -> \$${it.total} USD") }
)
// Aggregate results
val totalRevenue = processed.sumOf { it.total }
val avgOrder = processed.map { it.total }.average()
println("\nRevenue: \$$totalRevenue USD | Avg: \$${"%.2f".format(avgOrder)} USD")
// Functional composition with let/also/run
val report = orders
.filter { it.status != "CANCELLED" }
.groupBy { it.customer }
.mapValues { (_, list) ->
list.sumOf { it.total }
}
.also { println("\nRevenue by customer:") }
.map { (customer, total) -> "$customer: \$$total USD" }
.joinToString("\n ")
println(" $report")
}
出力:
TEXT
📖 参照専用
Processed: ORD-001 -> $239.992 USD
Processed: ORD-002 -> $12000.0 USD
Processed: ORD-005 -> $6400.0 USD
Revenue: $18639.992 USD | Avg: $6213.330666666667 USD
Revenue by customer:
Alice: $299.99 USD
Bob: $23000.0 USD
Charlie: $2500.0 USD
❓ よくある質問
Q ラムダ式と匿名関数の違いは何ですか?
A ラムダ式は
it を暗黙のパラメータとして使用し、return の挙動が異なります(ラムダ式 return は非局所的ですが、匿名関数 return は局所的です)。ほとんどの場合はラムダ式を使用してください。Q
it と明示的な命名、どちらをいつ使うべきですか?A ラムダ式の本体が1~2行で、文脈が明確な場合は
it を使用してください。本体が長かったり、ネストされたラムダ式がある場合は、可読性を高めるために明示的な名前を使用してください。Q すべての高階関数は
inline であるべきでしょうか?A いいえ。
inline は、ラムダ式が直接呼び出されるような小さな関数本体に最適です。大規模な関数をインライン化すると、コードサイズが大きくなります。 標準ライブラリのほとんどの高階関数は inline です。Q
reified は通常のクラスで使用できますか?A いいえ。
reified は inline 関数の型パラメータに対してのみ機能します。これは、インライン化によってコンパイラが呼び出し箇所で型情報を保持できるようになるためです。 通常の関数の型パラメータは実行時に消去されます。Q 末尾のラムダ記法は単なる構文上の糖衣に過ぎないのでしょうか?
A 構文上の糖衣ではありますが、極めて重要なものです。これはKotlinのDSL構築の基盤となっています。GradleのKotlin DSL、Compose UI、Ankoなどはすべて、この規約に依存しています。
Q
let/also/apply/run のうち、どれを選べばよいですか?A
letは値の変換用、alsoは副作用用、applyはオブジェクトの設定用(thisを返す)、runはブロックの実行用(結果を返す)です。 注意:applyはthisを返し、runはラムダ式の結果を返します。📖 まとめ
- デフォルト引数と名前付き引数により、Javaのメソッドオーバーロードが不要になる――1つの関数ですべての呼び出しの組み合わせを処理できる
- 高階関数は「何が変化するか」をパラメータ化することで、重複するロジックを排除する
- ラムダ構文は、完全な形式から簡潔な形式へと変化する:
{ x:Type -> ... }→{ x -> ... }→{ it -> ... }→ 末尾ラムダ - 末尾ラムダ表記の規約は、KotlinのDSLにおける構文上の基礎となっている
inlineはラムダオブジェクトの割り当てに伴うオーバーヘッドを排除し、reifiedはジェネリック型の情報を保持するlet/also/apply/runは、一般的に使用されるスコープ関数であり、それぞれ異なる目的を持っています
📝 練習問題
- 初心者 (⭐): デフォルト引数を持つ
calculateTotal関数を作成し、名前付き引数を使って呼び出してください。ヒント:fun calc(price: Double, tax: Double = 0.08) - 中級 (⭐⭐): 高階関数を用いて
processAndTransformを実装し、フィルタ条件と変換関数を受け取り、結果のリストを返すようにしてください。ヒント:<T> fun List<T>.process... - 課題 (⭐⭐⭐): 混合型のリストから、指定された型の要素をフィルタリングする
filterByType<Any>関数を実装してください。ヒント:inline fun <reified T> List<Any>.filterByType(): List<T>