Kotlin学習路(七):高次関数とインライン関数の関係

6115 ワード

使用の定義
高次関数:ある関数がパラメータとして別の関数を受信したり、戻り値のタイプが別の関数である場合、この関数を高次関数と呼びます.
構文規則:(String,Int)->Unit 1、->左側の部分は、関数がどのパラメータを受信するかを宣言するために使用され、複数のパラメータの間はカンマで区切られ、パラメータが受信されない場合は、()->Unitなどのカッコが使用されます.2、右側では関数が返す値のタイプを宣言し、返さない場合はUnitを使用します.これはvoidに相当します.
たとえば、上記の関数タイプを関数のパラメータ宣言または戻り値宣言に追加すると、この関数は高次関数になります.
fun example(func: (String, Int) -> Unit){
func("aaa", 123)
}
ここで、example関数は関数タイプのパラメータを受信するので、exampleは高次関数です.
高次関数を使用すると、関数タイプのパラメータによって関数の実行ロジックを決定できます.同じ関数パラメータであっても、その実行ロジックと最終的な戻り結果はまったく異なる可能性があります.
たとえば、メソッドnum 1 AndNum 2()の高次関数を定義し、2つの整数パラメータと1つの関数タイプパラメータを受信し、最終的に演算結果を返します.
fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int{
    val result = operation(num1, num2)
    return result
}
num 1 AndNum 2()関数の3番目のパラメータは、2つの整数パラメータを受信し、戻り値を持つ関数パラメータであり、ここでは、上記の関数パラメータタイプと一致する2つの方法を定義する必要がある.
fun plus(num1: Int, num2: Int) : Int{
    return num1 + num2
}
fun minus(num1: Int, num2: Int) : Int{
    return num1 - num2
}
この2つの関数のパラメータ戻りタイプはnum 1 AndNum 2()の関数パラメータタイプと全く同じです.次に、この方法を使用します.
fun main(){
    val num1 = 100
    val num2 = 10
    val result1 = num1AndNum2(num1, num2, :: plus)
    val result2 = num1AndNum2(num1, num2, :: minus)
    print("result1 is $result1")
    print("result2 is $result2")
}
ここで3番目のパラメータは::plus,::minusという書き方を用い,plusとminus関数をパラメータとしてnum 1 AndNum 2()関数に渡すことを示す関数の参照方法である.
この関数を用いて参照する書き方は正常に動作するが,呼び出すたびに一致する方法を定義する必要があり,煩雑であり,ここではLambda式に置き換える方法で実現できる.
上記のコードは、次のように変更できます.
fun main(){
    val num1 = 100
    val num2 = 10
    val result1 = num1AndNum2(num1, num2){
        n1, n2 -> n1 + n2
    }
    val result2 = num1AndNum2(num1, num2){
        n1, n2 -> n1 - n2
    }
Lambda式は、同じオブジェクトの複数のメソッドを連続的に呼び出す必要がある場合に、StringBuilderなどのコードをより簡素化するための指定されたコンテキストを提供します.例:
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
	block()
	return this
}
ここではStringBuilderにbuild拡張関数を定義し、この拡張関数は関数パラメータを受信し、StringBuilderタイプ値を返します.関数の前にClassNameを付ける.この関数タイプがどのクラスに定義されているかを示します.
val list  = listOf("a", "b", "c")
val result = StringBuilder().build {
    for (s in list){
        append(s)
    }
}
print(s)
ここでbuild関数の使い方はapplyの使い方と同じで、唯一の違いはbuild関数がStringBuilderに作用するだけで、apply関数はすべてのクラスに作用するので、Kotlin汎用を借りる必要があります.
げんり
高次関数がどのように使われているかは分かりますが、その原理を知る必要があります.また、上記のコードを例にnum 1 AndNum 2()関数を呼び出し、Lambda式で2つの整数パラメータを入力し、コードをJavaコードに変換すると、次のようになります.
public static int num1AndNum2(int num1, int num2, Function operation){
    int result = (int)operation.invoke(num1, num2);
    return result;
}
public static void main(){
    int num1 = 100;
    int num2 = 10;
    int result = num1AndNum2(num1, num2, new Function() {
        @Override
        public int invoke(Integer n1, Integer n2) {
            return n1 + n2;
        }
    });
}
ここでnum 1 AndNum 2()関数の3番目のパラメータはFunctionインタフェースになります.これはKotlinの内蔵インタフェースで、invoke()関数が実装され、num 1 AndNum 2()関数が呼び出されると、前のLambda式はここでFunctionインタフェースの匿名実装クラスになります.
したがって、Lambda式を呼び出すたびに、新しい匿名クラスインスタンスが作成され、メモリとパフォーマンスのオーバーヘッドが発生します.
inline
この問題を解決するために、Kotlinはインライン関数を提供し、インライン関数の使い方は非常に簡単で、高次関数を定義するときにinlineキーワードを加えるだけで、上記のコードは
inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int{
    val result = operation(num1, num2)
    return result
}
まず、KotlinコンパイラはLambda式のコードを関数タイプパラメータ呼び出しの場所に置き換えます.次に、インライン関数のすべてのコードを関数呼び出しの場所に置き換えます.最終的には、2つのInt直接加算に置き換えられます.
noinline
1つの高次関数が複数の関数パラメータタイプを受信すると、inlineはすべてのLambda式を自動的にインラインします.インライン期間の1つだけをインラインしたい場合は、inlineは満たされません.ここではnoinlineキーワードを使用する必要があります.例:
inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit){

}
ここではinline宣言inlineTest関数を使用しており、元々block 1、block 2はインラインされていたが、block 2の前にキーワードnoinlineを付けると、block 1のみインラインされる.
inlineとoninlineの違い:
キーワードinline:インラインされた関数パラメータタイプは、コンパイル時にコールバックしてコード置換されるため、真のパラメータ属性はありません.参照される式ではreturnキーワードを使用して関数を返すことができます.キーワードnoinline:非インライン関数パラメータタイプは、実際のパラメータであるため、他の任意の関数に自由に渡すことができます.インライン関数パラメータは、ローカルに戻ることしかできません.例:
fun printString(str: String, block: (String) -> Unit){
    block(str)
}
fun main(){
    val str = ""
    printString(str){
        s -> 
        if (s.isEmpty())
            return@printString
        print(s)
 print("END")
    }
}
ここではprintStringの高次関数を定義し、Lambda式に印刷された文字列を入力し、文字列パラメータが空の場合は印刷しません.
Lambdaではreturnキーを直接使用することはできませんので、ここではreturn@printStringローカルに返され、後続のコードが実行されないことを示し、Javaのreturnと同じ機能を果たします.
入力されたパラメータが空の文字列の場合、return以降の文は実行されません.
ただしprintString関数をインライン関数として宣言する場合は、Lambdaでreturnキーを使用することもできます.例:
inline fun printString(str: String, block: (String) -> Unit){
    block(str)
}
fun main(){
    val str = ""
    printString(str){
        s -> 
        if (s.isEmpty())
            return
        print(s)
 print("END")
    }
}
ここでreturnは、戻りレイヤの呼び出し関数、すなわちmain関数を表す.
crossinline
ほとんどの高次関数はインライン関数を宣言することができ、一部はだめです.例:
inline fun runRunnable(block: () ->vUnit){
	val runnable = Runnable{
		block()
	}
	runnable.run()
}
上記のコードはinline宣言なしで正常に動作しますが、inlineを付けるとエラーが表示されます.
RunRunable関数でRunableオブジェクトを作成し、RunableのLambda式で入力した関数パラメータを呼び出します.
一方,Lambda式はコンパイル時に匿名クラスの実現方式に変換され,実際には上記のコードは匿名クラスで入力された関数パラメータを呼び出している.
一方、インライン関数が参照するLambdaではreturnを使用して関数を返すことができますが、実際に匿名クラスで呼び出された関数パラメータのため、外層呼び出し関数の返すことはできません.匿名クラスの関数呼び出しのみを返すことができます.
すなわち,高次関数でLambdaまたは匿名クラスの実装を作成し,これらの実装で関数パラメータを呼び出した場合,高次関数をインライン関数として宣言すると,必ずエラーが報告される.
この場合はcrossinlineキーワードを使用する必要があります.上記のコードは、次のように変更できます.
inline fun runRunnable(crossinline block: () ->vUnit){
	val runnable = Runnable{
		block()
	}
	runnable.run()
}
これで正常にコンパイルできます.
インライン関数のLambda式ではreturnを使用できますが、高次関数の匿名クラスではreturnを使用できません.これは競合を引き起こし、crossinlineはこの競合を解決します.crossinlineが宣言されるとrunRunable関数を呼び出すことができないときのLambda式でreturnを使用して関数を返しますが、まだ使用できます.return@runRunableで行ないます.