インライン関数 — コンパイル時に関数本体が各呼び出し箇所に直接代入されるKotlinの仕組み。これにより、ラムダパラメータのための匿名クラスやオブジェクトの作成によるオーバーヘッドが排除されます。Kotlin Documentation, 2025によると、inlineキーワードは高階関数に特に効果的で、各ラムダがインライン化されないと個別のFunctionNオブジェクトを作成し、ガベージコレクタに負荷をかけます。
重要ポイント
インライン関数とは、inlineキーワードでマークされた関数です。Kotlinコンパイラはそれ用の個別のバイトコードを作成せず、関数本体を各呼び出し箇所に直接コピーします。主な目的は、ラムダ式を受け取る高階関数を最適化することです。通常、各ラムダは匿名のFunctionクラスオブジェクトを作成するためです。
JetBrains Tech Blog(2024)によると、Kotlinでインライン関数を使用すると、ラムダを集中的に使用する関数で作成されるオブジェクトの数を40~60%削減できます。ループや高負荷操作(ソート、コレクションフィルタリング)では、測定可能なパフォーマンス向上が得られます。
インライン化しない場合、各ラムダは匿名クラス(または合成された関数型インターフェースのインスタンス)にコンパイルされます。変数をキャプチャするラムダの場合、追加のラッパーオブジェクトが作成されます。インライン関数はコンパイル時にこれらすべてのオブジェクトを排除し、ラッパーなしでローカル変数にアクセスする直接コードに置き換えます。
inlineはラムダパラメータを持つ関数にのみ使用してください。Kotlinコンパイラは、inlineがメリットをもたらさない場合に警告を発します。
関数宣言の前にinlineキーワードを追加するだけです。コンパイラは自動的に呼び出し箇所で関数本体を置き換えます。関数自体は、直接呼び出されない場合(Javaコードからなど)のためにバイトコードに残ります。
inline fun Int.repeatAction(action: (Int) -> Unit) {
for (i in 0 until this) {
action(i)
}
}
// 呼び出し — ラムダコードが関数本体にインライン化される
5.repeatAction { index ->
println("Index: $index")
}
コンパイル後、上記のコードは以下と同等になります:
// インライン化後の動作(概略):
val $this = 5
for (i in 0 until $this) {
println("Index: $i")
}
ラムダ用のオブジェクトは作成されません — actionコードが直接実行されます。これが最適化の本質です:Function.invoke()を呼び出す代わりに、ラムダ本体を含むコードが直接挿入されます。
インライン化を確認するには、IntelliJ IDEAでTools > Kotlin > Show Kotlin Bytecodeを開き、Decompileをクリックします。ラムダ付きのrepeatActionを呼び出す代わりに、forループ付きの関数本体が直接挿入されていることがわかります。
Kotlinの各ラムダは3つのバリアントのいずれかにコンパイルされます。1つ目 — ラムダが変数をキャプチャしない場合、宣言されたクラスの静的メソッドになります。2つ目 — 1つの変数をキャプチャする場合、匿名クラスが作成されます。3つ目 — 複数の変数をキャプチャする場合、キャプチャされた各変数のフィールドを持つ匿名クラスが作成されます。
| ラムダの種類 | inlineなし | inlineあり |
|---|---|---|
| キャプチャなし | 1つの静的メソッド(再利用) | 完全インライン化、呼び出しなし |
| 1変数キャプチャ | 匿名クラス(1オブジェクト) | 完全インライン化、オブジェクトなし |
| N変数キャプチャ | Nフィールドの匿名クラス | 完全インライン化、オブジェクトなし |
| 再帰的 | 通常の呼び出し | inline禁止 |
Android Performance Patterns(Google、2024)によると、コレクションを集中的に使用するアプリケーション(フィルタリング、ソート、グルーピング)では、インライン関数により割り当てが25~35%削減されます。この効果は、状態変更ごとに多数のラムダで再コンポジションが発生するJetpack Composeで特に顕著です。
通常の関数のラムダは外部関数からreturnできません — ラムダ自体からのローカルリターン(return@label経由)のみ可能です。インライン関数では、ラムダが呼び出し元関数の本体にインライン化されるため、非ローカルリターンが可能になります:ラムダ内のreturnが外部関数を終了させます。
inline fun findFirst(
items: List<Int>,
predicate: (Int) -> Boolean
): Int {
for (item in items) {
if (predicate(item)) {
return item
}
}
return -1
}
fun processNumbers() {
val numbers = listOf(1, 2, 3)
val firstEven = findFirst(numbers) { it % 2 == 0 }
// ラムダ内のreturnはprocessNumbers()からnullを返す
}
非ローカルリターンは早期終了に便利ですが、エラーの原因になる可能性があります。ラムダが非ローカルコンテキストで使用される場合(変数に格納)、非ローカルリターンはRuntimeExceptionを引き起こします。Kotlinコンパイラはそのような格納を試みると警告を発します。
関数に複数のラムダパラメータがある場合、その一部のみをインライン化する必要があることがあります。これにはnoinlineを使用します — 特定のラムダパラメータのインライン化を防ぎ、通常のFunctionオブジェクトとして残します。
crossinline修飾子は逆の問題を解決します:ラムダはインライン化されますが、非ローカルリターンは禁止されます。これはラムダが別のラムダ内部やreturnが許可されないコンテキスト(Runnableに渡されるなど)で使用される場合に必要です。
inline fun processWithCallback(
data: String,
crossinline onSuccess: (String) -> Unit,
noinline onError: (Exception) -> Unit
) {
try {
val result = process(data)
onSuccess(result)
} catch (e: Exception) {
onError(e)
}
}
// noinline: onErrorは変数に格納または他の場所に渡すことができる
val errorHandler = { e: Exception -> log(e.message) }
processWithCallback("input", { println(it) }, errorHandler)
例では、onSuccessはcrossinlineとしてマークされています — インライン化されますが、内部でreturnを使用できません。onErrorはnoinlineとしてマークされています — インライン化されないため、オブジェクトとして渡したり、クラスフィールドに格納したり、リスナーとして使用したりできます。
インライン関数には制限があります。再帰的インライン関数は禁止されています — コンパイラがエラーを返します。インライン関数は別のモジュールで宣言された場合、privateやinternalの可視性を持つことができませんが、これはインライン化機構自体ではなく可視性の制限です。
関数本体がコピーされるため、インライン関数の呼び出しごとにバイトコードサイズが増加します。Kotlin Coding Conventions(JetBrains、2025)によると、inlineは10~15行までの関数にのみ使用することを推奨しています。大きな関数の場合、ラムダのインライン化の利点がAPKサイズの増加によって相殺される可能性があります(Androidでは64Kメソッド制限のため重要)。
// 推奨される方法
inline fun withLock(lock: Lock, action: () -> T): T {
lock.lock()
try {
return action()
} finally {
lock.unlock()
}
}
// 大きな関数には非推奨
inline fun largeComputation(...) { // 不良 — 本体 >50行
// 50行超 — 通常の関数に抽出する方が良い
}
ライブラリ内のパブリックインライン関数には注意が必要です:インライン関数の本体が変更された場合、すべてのクライアントが再コンパイルする必要があります。JetBrainsは、モジュール内の互換性を維持するために、インライン関数から呼び出されるメンバーに@PublishedApi internalを使用することを推奨しています。
よくある質問
はい、インライン拡張関数は制限なく動作します。例:inline fun String.transform(block: (Char) -> Char): String。拡張はインライン化の能力に影響しません — コンパイラは通常のインライン関数と同様に処理します。
関数がラムダパラメータを受け取らない場合 — inlineは利点をもたらしません。Kotlinコンパイラは警告を発します:“Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types.” また、バイトコード増加のため、大きな関数にはinlineは有害です。
inline — 関数本体を呼び出し箇所にインライン化する関数修飾子。@JvmInline(value class)— コンパイル時に値で置き換えられるラッパークラスの仕組み。異なる概念です:inlineは呼び出しを最適化し、value classはデータ表現を最適化します。
いいえ、suspend関数はContinuationを持つステートマシンにコンパイルされるため、inlineにできません。ただし、インライン関数はcrossinlineを付けてパラメータとしてsuspendラムダを受け取ることができます。これはコルーチンでよく使用されます:inline fun launch(block: suspend CoroutineScope.() -> Unit)。
はい、インライン関数はデバッグを複雑にします。関数本体が呼び出されるのではなく、呼び出し箇所にインライン化されるためです。スタックトレースが長くなり、ブレークポイントは機能しますが、予期しない位置を示す可能性があります。JetBrainsはinlineなしでデバッグし、リリースビルドでのみ有効にすることを推奨しています。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。