この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
まえがき
Kotlinのスコープ関数便利ー。
let apply run with also
ただ本当に使いこなすに ラムダ と レシーバー指定ラムダ を理解しなければなりません。
// ラムダ
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
// レシーバー指定ラムダ
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
block: (T) -> R) と block: T.() -> Unit の違いわかりますか
これを理解としないとblock内の this の罠に引っかかる
サンプル
理解するためのサンプルコードを書きました
class Sample {
val lambda: (String) -> Unit = {
Log.d("test", this.javaClass.simpleName) //Sample
Log.d("test", it) //test
}
val receiverLambda: String.() -> Unit = {
Log.d("test", this.javaClass.simpleName) //String
Log.d("test", this) //test
}
}
thisの値が違いますね。なぜこういう動きになるのかKotlin BytecodeをDecompileにして、おってみましょう。
Kotlin BytecodeからDecompile
Android Studioをもっていれば簡単にKotlin BytecodeをDecompileできます。
Decompileしたものがこちら。
@Metadata(
mv = {1, 1, 6},
bv = {1, 0, 1},
k = 1,
d1 = {"\u0000$\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\u0010\u000e\n\u0002\u0010\u0002\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0002\b\u0002\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002R\u001d\u0010\u0003\u001a\u000e\u0012\u0004\u0012\u00020\u0005\u0012\u0004\u0012\u00020\u00060\u0004¢\u0006\b\n\u0000\u001a\u0004\b\u0007\u0010\bR\"\u0010\t\u001a\u0013\u0012\u0004\u0012\u00020\u0005\u0012\u0004\u0012\u00020\u00060\u0004¢\u0006\u0002\b\n¢\u0006\b\n\u0000\u001a\u0004\b\u000b\u0010\b¨\u0006\f"},
d2 = {"Lcom/kamedon/devio2017/validation/Sample;", "", "()V", "lambda", "Lkotlin/Function1;", "", "", "getLambda", "()Lkotlin/jvm/functions/Function1;", "receiverLambda", "Lkotlin/ExtensionFunctionType;", "getReceiverLambda", "production sources for module app"}
)
public final class Sample {
@NotNull
private final Function1 lambda = (Function1)(new Function1() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object var1) {
this.invoke((String)var1);
return Unit.INSTANCE;
}
public final void invoke(@NotNull String it) {
Intrinsics.checkParameterIsNotNull(it, "it");
Log.d("test", Sample.this.getClass().getSimpleName());
Log.d("test", it);
}
});
@NotNull
private final Function1 receiverLambda;
@NotNull
public final Function1 getLambda() {
return this.lambda;
}
@NotNull
public final Function1 getReceiverLambda() {
return this.receiverLambda;
}
public Sample() {
this.receiverLambda = (Function1)null.INSTANCE;
}
}
ラムダ
private final Function1 lambda = (Function1)(new Function1() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object var1) {
this.invoke((String)var1);
return Unit.INSTANCE;
}
public final void invoke(@NotNull String it) {
Intrinsics.checkParameterIsNotNull(it, "it");
Log.d("test", Sample.this.getClass().getSimpleName());
Log.d("test", it);
}
});
ラムダは、Function1というインターフェースがつくられ、invokeという関数ができます。itという名のStringを引数なっていますね。
thisはFunction1インターフェースを実装した場所すなわち、Sampleになります。
レシーバー指定ラムダ
d2 = {"Lcom/kamedon/devio2017/validation/Sample;", "", "()V", "lambda", "Lkotlin/Function1;", "", "", "getLambda", "()Lkotlin/jvm/functions/Function1;", "receiverLambda", "Lkotlin/ExtensionFunctionType;", "getReceiverLambda", "production sources for module app"}
ExtensionFunctionTypeつまり、拡張関数です。この場合はStringにgetReceiverLambdaというメソッドを作っていますね このFunction1の定義場所はStringなのでthisはStringになります。
こんなこともできちゃいます。
class Sample {
//この関数内で使用できる拡張関数。Stringにlog()を拡張
fun log(log: String.() -> Unit) {
"test".log()
"hoge".log()
"foo".log()
}
fun showLog() {
log { print(this) }
log { Log.d("hoge", this) }
}
}
fun log()内だけで有効なString拡張関数をいれるイメージです。これを利用するとKotlinっぽいDSLが作りやすくなりますので、何度サンプルを書いて試してみることをおすすめします。
まとめ
ラムダとレシーバー指定ラムダはなんとなくイメージできましたか?
レシーバーって結局なんだ?っと思った方いるかもしれません。それは次回の話のネタに。