クロージャ
関数の中に関数を書くことができます。
fun outer() { fun inner() { } }
通常の関数
fun inner() { }
匿名関数
val inner = fun() { }
val inner = { }
記法はいくつかあれど、Kotlinの場合関数の中に書く関数はすべてクロージャとなります。
つまり内側の関数は外側の関数にあるローカル変数へのアクセスが可能です。
fun enclosure() { val localVariable = 42 fun closure1(): Int { return localVariable + 1 } val closure2 = fun(): Int { return localVariable + 1 } val closure3: () -> Int = { localVariable + 1 } }
このキャプチャを実現している手法についてです
用語
どの記法を使ったとしてもクロージャはJVMの class
へとコンパイルされます (Kotlin 1.4.10)
final class enclosure$1 implements kotlin.jvm.functions.Function0<Integer> { final int $a; enclosure$1(final int a) { this.$a = a; } @Override public final Integer invoke() { return Integer.valueOf(invoke()); } public final int invoke() { return this.$a + 1; } }
invoke()
をふたつ定義しているので実際にはこのJavaコードはコンパイルできませんが、
クラスファイルとしてはこれは有効なためこの形式へと変換されます。
より厳密に言えば型引数である <Integer>
はイレイジャによって消されて Object
になります。
public final Object invoke() { // ^^^^^^ return Integer.valueOf(invoke()); }
簡単ですね。
少しややこしくなってくるのが、 エンクロージャの変数が再代入可能な場合です。
fun enclosure() { var localVariable = 42 fun closure1(): Int { localVariable++ return localVariable } }
Javaだとコンパイルできないやつです。
Javaのクロージャがクロージャじゃないとか言われる所以ですね。今回はそんな話はしたくないので割愛します
クロージャが再代入した値はエンクロージャから見ても反映されているし逆もまた然りです。
この場合にKotlinのコンパイラが生成するclassは下記のようになります。
final class enclosure$1 implements kotlin.jvm.functions.Function0<Integer> { final kotlin.jvm.internal.Ref.IntRef $a; enclosure$1(final kotlin.jvm.internal.Ref.IntRef a) { this.$a = a; } @Override public final Integer invoke() { return Integer.valueOf(invoke()); } public final int invoke() { this.$a.element++; return this.$a.element; } }
Ref.IntRef
でラップ(wrap)されます。
だいたい想像できるかと思いますがこうなっています。
public static final class IntRef implements Serializable { public int element; }
この場合エンクロージャ側のキャプチャされる変数は暗黙的に Ref.IntRef
型に変換されます。
public final void enclosure() { final Ref.IntRef localVariable = new Ref.IntRef(); localVariable.element = 42; enclosure$1 closure1 = new enclosure$1(localVariable); }
そりゃそうですね。
ただし inline fun
でクロージャがインライン展開される場合にはこのwrapは発生せず
通常通りローカル変数のままコンパイルされます。
inline fun
についてはまたいつか書くかもしれません