1. 概要

このクイックチュートリアルでは、レシーバーを備えたラムダとは何か、そしてそれらがどのようにしてより単純で読みやすくなるかを見ていきます。

2. レシーバー付きラムダ

入力としてラムダを受け入れるKotlinのバニラ拡張関数から始めましょう。

fun <T> T.applyThenReturn(f: (T) -> Unit): T {
    f(this)
    return this
}

この拡張関数を使用すると、関数を値に適用して同じ値を返すことができます。

val name = "Baeldung".applyThenReturn { n -> println(n.toUpperCase()) }

上に示したように、値を操作するには、ラムダ引数を使用する必要があります。 Kotlinの通常のLambda関数は、そのようなものです。明示的な引数のセットと、->。で区切られたラムダの本体です。

これは素晴らしいことですが、次のようなことができたらどうでしょうか。

val name = "Baeldung".applyThenReturn { println(toUpperCase()) }

ラムダ引数(上記の例では n )を操作する代わりに、ここではこの参照でtoUpperCase()メソッドを呼び出しています。 基本的に、修飾されていない各関数呼び出しは、呼び出しの受信者として“ Baeldung”文字列を使用しているふりをしています。 これにより、ラムダ本体がより簡潔になります。

結局のところ、この形式のラムダ式は、Kotlinでレシーバーを使用したラムダによって実際に可能です。 この機能を使用するには、ラムダ式を少し異なる方法で定義する必要があります。

fun <T> T.apply(f: T.() -> Unit): T {
    f() // or this.f()
    return this
}

上に示したように、typeパラメーターを括弧の外に移動しました。 また、 f(this)の代わりに、 this.f()と等しい f()関数を呼び出すだけです。 この場合も、修飾されていない各関数呼び出しは、Tのインスタンスを呼び出しの受信者として使用します。

標準ライブラリとサードパーティライブラリはどちらも、開発者エクスペリエンスを向上させるために、レシーバーでラムダを広く使用しています。 apply() scope関数を含む、Kotlinのいくつかの組み込みスコープ関数がこの機能を使用しています。

inline fun <T> T.apply(block: T.() -> Unit): T {
    // omitted
    block()
    return this
}

3. バイトコード表現

これらの2つの関数がバイトコードレベルでどのように異なるかを確認するために、それぞれをコンパイルしてみましょう。 これを行うには、kotlincユーティリティを使用できます。 コンパイル後、 javap ツールを使用して、生成されたバイトコードを確認できます。

>> kotlinc Receiver.kt
>> javap -c -p com.baeldung.receiver.ReceiverKt
public static final <T> T applyThenReturn(T, kotlin.jvm.functions.Function1<? super T, kotlin.Unit>);
    Code:
       // omitted
       6: aload_1
       7: aload_0
       8: invokeinterface #49, 2 // InterfaceMethod Function1.invoke:(Ljava/lang/Object;)Ljava/lang/Object;

したがって、ご覧のとおり、Kotlinコンパイラは applyThenReturn 関数を2つのパラメータを受け入れる静的メソッドに変換します。1つは拡張関数レシーバーを渡し、もう1つはラムダ本体をカプセル化します。 ここでは、 Function1 <? スーパーT、kotlin.Unit> 何も返さない、または何も返さない1つの入力を持つ関数です単位

さらに、ラムダを呼び出すには、最初のパラメーター( aload_0 )をinvokeメソッドに渡すだけです。 驚いたことに、レシーバーを備えたラムダのバイトコードは上記とまったく同じです。

同様に、呼び出しサイトの1つであるKotlinは、両方の関数呼び出しを単純な静的メソッド呼び出しに変換します。

肝心なのは、 通常のラムダとレシーバー付きのラムダはコンパイル時に異なりますが、バイトコードレベルではまったく同じです。 

4. 結論

このチュートリアルでは、最初に、レシーバーでラムダを利用して、より優れた、より読みやすいプログラム構成を作成する方法を確認しました。 APIに精通することに加えて、このタイプのラムダがバイトコードレベルでどのように表されるかについても学びました。

いつものように、すべての例はGitHubから入手できます。