Skip to content

インライン関数

高階関数を使用すると、いくつかの実行時ペナルティが発生します。各関数はオブジェクトであり、クロージャをキャプチャするからです。クロージャは、関数の本体でアクセスできる変数のスコープです。 メモリ割り当て(関数オブジェクトとクラスの両方)と仮想呼び出しは、実行時オーバーヘッドを発生させます。

しかし、多くの場合、この種のオーバーヘッドはラムダ式のインライン化によって解消できることがわかっています。 以下の関数は、この状況の良い例です。lock()関数は呼び出し箇所(コールサイト)で簡単にインライン化できます。 次のケースを考えてみましょう。

kotlin
lock(l) { foo() }

パラメータの関数オブジェクトを作成して呼び出しを生成する代わりに、コンパイラは次のコードを生成できます。

kotlin
l.lock()
try {
    foo()
} finally {
    l.unlock()
}

コンパイラにこれを行わせるには、lock()関数をinline修飾子でマークします。

kotlin
inline fun <T> lock(lock: Lock, body: () -> T): T { ... }

inline修飾子は、関数自体とその関数に渡されるラムダの両方に影響を与え、それらすべてが呼び出し箇所にインライン化されます。

インライン化によって生成されるコードが増大する可能性があります。ただし、適切な方法で(大規模な関数のインライン化を避けて)行えば、パフォーマンスが向上し、特にループ内の「メガモルフィック」な呼び出し箇所で効果を発揮します。

noinline

インライン関数に渡されるすべてのラムダがインライン化されることを望まない場合、関数の一部のパラメータをnoinline修飾子でマークします。

kotlin
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { ... }

インライン化可能なラムダは、インライン関数内で呼び出すか、インライン化可能な引数として渡すことしかできません。 しかし、noinlineラムダは、フィールドに保存したり、あちこちに渡したりするなど、好きなように操作できます。

NOTE

インライン関数にインライン化可能な関数パラメータがなく、実体化された型パラメータもない場合、コンパイラは警告を発します。そのような関数のインライン化は、ほとんどの場合、有益ではないためです(インライン化が必要であると確信している場合は、@Suppress("NOTHING_TO_INLINE")アノテーションを使用して警告を抑制できます)。

非ローカルジャンプ式

Returns

Kotlinでは、通常の非修飾returnは、名前付き関数または匿名関数を終了するためにのみ使用できます。 ラムダを終了するには、ラベルを使用します。ラムダ内での裸のreturnは禁止されています。ラムダが囲む関数をreturnさせることができないためです。

kotlin
fun ordinaryFunction(block: () -> Unit) {
    println("hi!")
}
fun foo() {
    ordinaryFunction {
        return // ERROR: `foo`をここでreturnさせることはできません
    }
}
fun main() {
    foo()
}

しかし、ラムダが渡される関数がインライン化される場合、returnもインライン化できます。したがって、これは許可されます。

kotlin
inline fun inlined(block: () -> Unit) {
    println("hi!")
}
fun foo() {
    inlined {
        return // OK: ラムダがインライン化されます
    }
}
fun main() {
    foo()
}

このようなreturn(ラムダ内に配置されているが、囲む関数を終了するもの)は、非ローカルリターンと呼ばれます。この種の構成は、インライン関数がよく囲むループ内で通常発生します。

kotlin
fun hasZeros(ints: List<Int>): Boolean {
    ints.forEach {
        if (it == 0) return true // hasZerosからreturnします
    }
    return false
}

一部のインライン関数は、パラメータとして渡されたラムダを関数の本体から直接ではなく、ローカルオブジェクトやネストされた関数など、別の実行コンテキストから呼び出す場合があることに注意してください。そのような場合、ラムダ内で非ローカル制御フローは許可されません。インライン関数のラムダパラメータが非ローカルリターンを使用できないことを示すには、ラムダパラメータをcrossinline修飾子でマークします。

kotlin
inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run() = body()
    }
    // ...
}

Breakとcontinue

DANGER

この機能は現在プレビュー中です。

将来のリリースで安定化する予定です。

有効にするには、-Xnon-local-break-continueコンパイラオプションを使用してください。

YouTrackでのフィードバックをお待ちしております。

非ローカルなreturnと同様に、ループを囲むインライン関数への引数として渡されるラムダ内でbreakおよびcontinueジャンプ式を適用できます。

kotlin
fun processList(elements: List<Int>): Boolean {
    for (element in elements) {
        val variable = element.nullableMethod() ?: run {
            log.warning("Element is null or invalid, continuing...")
            continue
        }
        if (variable == 0) return true
    }
    return false
}

実体化された型パラメータ

場合によっては、パラメータとして渡された型にアクセスする必要があります。

kotlin
fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
    var p = parent
    while (p != null && !clazz.isInstance(p)) {
        p = p.parent
    }
    @Suppress("UNCHECKED_CAST")
    return p as T?
}

ここでは、ツリーをたどり、リフレクションを使用してノードが特定の型であるかどうかを確認しています。 これでも問題ありませんが、呼び出し箇所はあまりきれいではありません。

kotlin
treeNode.findParentOfType(MyTreeNode::class.java)

より良い解決策は、単にこの関数に型を渡すことです。次のように呼び出すことができます。

kotlin
treeNode.findParentOfType<MyTreeNode>()

これを可能にするために、インライン関数は実体化された型パラメータをサポートしているため、次のように書くことができます。

kotlin
inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p.parent
    }
    return p as T?
}

上記のコードは、reified修飾子で型パラメータを修飾し、まるで通常のクラスであるかのように関数内でアクセスできるようにします。 関数がインライン化されるため、リフレクションは必要なく、!isasなどの通常の演算子を使用できるようになります。 また、上記のようにmyTree.findParentOfType<MyTreeNodeType>()と関数を呼び出すことができます。

多くの場合、リフレクションは必要ないかもしれませんが、実体化された型パラメータと組み合わせて使用することはできます。

kotlin
inline fun <reified T> membersOf() = T::class.members

fun main(s: Array<String>) {
    println(membersOf<StringBuilder>().joinToString("
"))
}

通常の関数(インラインとしてマークされていない関数)は、実体化されたパラメータを持つことはできません。 実行時表現を持たない型(たとえば、実体化されていない型パラメータやNothingのような架空の型)は、実体化された型パラメータの引数として使用できません。

インラインプロパティ

inline修飾子は、バッキングフィールドを持たないプロパティのアクセサで使用できます。 個々のプロパティアクセサにアノテーションを付けることができます。

kotlin
val foo: Foo
    inline get() = Foo()

var bar: Bar
    get() = ...
    inline set(v) { ... }

プロパティ全体にアノテーションを付けることもでき、その場合は両方のアクセサがinlineとしてマークされます。

kotlin
inline var bar: Bar
    get() = ...
    set(v) { ... }

呼び出し箇所では、インラインアクセサは通常のインライン関数と同様にインライン化されます。

public APIインライン関数の制限

インライン関数がpublicまたはprotectedであり、かつprivateまたはinternal宣言の一部でない場合、それはモジュールのpublic APIとみなされます。 それは他のモジュールから呼び出すことができ、そのような呼び出し箇所でもインライン化されます。

これは、呼び出し元のモジュールが変更後に再コンパイルされない場合に、インライン関数を宣言するモジュールでの変更によってバイナリ互換性のリスクをもたらします。

モジュールのpublic APIの変更によってこのような非互換性が導入されるリスクを排除するために、public APIインライン関数は、privateおよびinternal宣言とその一部である非public API宣言をその本体で使用することはできません。

internal宣言には@PublishedApiをアノテーションとして付けることができ、これによりpublic APIインライン関数での使用が許可されます。 internalインライン関数が@PublishedApiとしてマークされている場合、その本体もpublicであるかのようにチェックされます。