by lazy in Kotlin
本日はKotlinについてです。
はじめに
By lazyに入る前に、デリゲート・パターンについて説明しましょう。
Delegationパターン**は、ソフトウェアデザインパターンの基本的なスキルです。Delegationパターンでは、**2つのオブジェクトが同じリクエストの処理に関与し、リクエストを受け取ったオブジェクトがそのリクエストを別のオブジェクトに委譲して処理させます。Delegationパターン** は基本的なテクニックであり、Stateパターン、Strategyパターン、Visitorパターンなど、他の多くのパターンも基本的にはDelegationパターンをより具体的なコンテキストで採用しています。デリゲートパターンは継承を集約に置き換えることを可能にし、ミキシンをシミュレートすることも可能にします。
まだ少し曖昧ですか? これがその例です。
class People {
fun sayHello() {
println("Hello World")
}
}
class Student {
fun sayHello() {
People().sayHello()
}
}
fun main() {
Student().sayHello()
}
//Hello World
とても簡単に思えて、またできるような気がしませんか? 素晴らしいことです。
本文の冒頭
デリゲートには属性デリゲートとクラスデリゲートの2種類があります。
クラスの委譲
以下のコードに関する2つの問題
- Kotlinでは、 by は委譲されたキーワードであり、lazyはキーワードではなく式です。
- Derivedがprintメソッドを実装する必要がない理由がわかりましたね。
プリントアウトは、BaseImplのインスタンスを作成し、それを渡し、Derivedにインスタンスを渡したことを示しており、プリントアウトもそれに対応しています。
BaseImplもBaseを実装しているので、DerivedはBaseImplに委譲して対応するメソッドを実行します。実際には最初の例と同じですが、Kotlinの構文シュガーによってメソッドを省略しやすくなっています。
interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } class Derived(b: Base) : Base by b fun main() { val b = BaseImpl(10) Derived(b).print() } 10
あなたは、これはフレンドリーなコードは読みやすいものではありませんと思うかもしれませんが、それについて考える場合は、メソッドのBaseは、数がある場合は、デリゲートを実装するためにキーワードを使用しない場合、あなたは1行のように単純にすることはできませんので、あなたは今、私はBaseImplは、メソッドのすべての実装をデリゲートしたくない質問を持っている可能性がありますどのようにそれを行うには、実際には、それはまた、あなたがしたくない非常に簡単です。実際には、それは非常に単純です、あなたはBaseImplは、その上に書き直さいくつかのメソッドを委譲したくありません。
interface Base { fun printMessage() fun printMessageLine() } class BaseImpl(val x: Int) : Base { override fun printMessage() { print(x) } override fun printMessageLine() { println(x) } } class Derived(b: Base) : Base by b { override fun printMessage() { print("abc") } } fun main() { val b = BaseImpl(10) Derived(b).printMessage() Derived(b).printMessageLine() }
この方法で書き換えられたメンバは、デリゲートオブジェクトのメンバ内から呼び出されることはありません:
interface Base { val message: String fun print() } class BaseImpl(val x: Int) : Base { override val message = "BaseImpl: x = $x" override fun print() { println(message) } } class Derived(b: Base) : Base by b { // `print` 実装はこのプロパティにアクセスしない override val message = "Message of Derived" } fun main() { val b = BaseImpl(10) val derived = Derived(b) derived.print() println(derived.message) } //BaseImpl: x = 10 //Message of Derived
属性の委譲
属性のデリゲートはクラスのデリゲートとは少し異なります。JAVAでこれを実装するのは簡単なことではありません。
Kotlinではlateinitキーワードはロードを遅延させるために使われるのでは? しかし、Kotlinにはlazyという別の遅延ロードの方法があります。lazyはオブジェクトが最初に使われるときに初期化されるのに対して、lateinitで定義された変数はnullでないことを保証するために使われる前にインスタンスが与えられます。 では、オブジェクトが最初に使われるときに初期化されるとはどういうことか見てみましょう。
class Bean { val str by lazy { println("Init lazy") "Hello World" } } fun main() { val bean = Bean() println("Init Bean") println(bean.str) println(bean.str) } //Init Bean //Init lazy //Hello World //Hello World
プリントアウトで確認できるはずです。 すごいでしょう? 最初に使うときだけ初期化します。
一般的なプロパティタイプはいくつもあり、必要になるたびに手作業で実装することも可能ですが、誰にでも一度だけ実装してライブラリにまとめておくといいでしょう。例えば、include:
- Delay属性:その値は最初のアクセスでのみ計算されます;
- Observable Property: リスナーは、このプロパティの変更を通知されます;
- 複数の属性を、それぞれ別のフィールドに存在するのではなく、1つのマッピングに格納します。
これらのケースをカバーするために、Kotlin は
LazyThreadSafetyMode.SYNCHRONIZED
lazy ドキュメント 作成された lazy の新しいインスタンスは、指定された初期化関数と デフォルトのスレッドセーフモードを使用して初期化されます。値の初期化が例外をスローした場合、次にその値にアクセスしたときに再初期化を試みます。返されたインスタンスは、それ自身の同期を使用することに注意してください。 予期せぬデッドロックを引き起こす可能性があるためです。 また、この動作は将来変更される可能性があります。彼のソースコードを見てください。
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
デフォルトでは、lazy プロパティの評価は同期的にロックされます: 値は1つのスレッドでのみ評価され、すべてのスレッドが同じ値を見ることになります。
LazyThreadSafetyMode.PUBLICATION
初期化デリゲートの同期ロックが不要で、複数のスレッドが同時に実行できる場合は、 lazy()関数の引数として渡してください。LazyThreadSafetyMode.NONE
また、初期化が常にプロパティの使用と同じスレッドで行われることが確実な場合は、このパターンを使用してください。val str by lazy(LazyThreadSafetyMode.NONE) { println("Init lazy") "Hello World" }
使用条件
読み取り専用プロパティの場合、デリゲートは以下のパラメータを持つ演算子関数 getValue() を提供しなければなりません:
- this Ref -- 型と同じか、そのスーパータイプでなければなりません。
- <*> property -- KProperty 型またはその上位型でなければなりません。
getValue() は、プロパティと同じ型を返す必要があります。
class Resource class Owner { val valResource: Resource by ResourceDelegate() } class ResourceDelegate { operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource { return Resource() } }
変数プロパティの場合、デリゲートはさらに、以下のパラメータを持つ演算子関数 setValue() を提供しなければなりません:
this Ref -- 型と同じか、そのスーパータイプでなければなりません。
<*> property -- KProperty 型またはその上位型でなければなりません。
value - 属性タイプと同じでなければなりません。
class Resource class Owner { var varResource: Resource by ResourceDelegate() } class ResourceDelegate(private var resource: Resource = Resource()) { operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource { return resource } operator fun setValue(thisRef: Owner, property: KProperty<*>, value: Any?) { if (value is Resource) { resource = value } } }
上のコードでは、もちろん、val immutableプロパティはgetValueメソッドだけを実装すればよく、var mutableプロパティはgetValueメソッドとsetValueメソッドの両方を実装すればよい、ということがよくわかります。とてもわかりやすいでしょう?
まとめ
以上の紹介を通して、by lazyは様々なby lazy操作を実現するために使用することができるということを、ある程度理解していただけたと思います。
abstract class BaseActivity : AppCompatActivity() {
protected inline fun <reified T : ViewDataBinding> binding(@LayoutRes contentLayoutId: Int) =
lazy {
DataBindingUtil.setContentView<T>(this, contentLayoutId)
.apply { this.lifecycleOwner = this@BaseActivity }
}
}
class MainActivity Fragment: BaseActivity()も同様である。{
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding by binding<ActivityMainBinding, MyViewModel>(R.layout.activity_main)
}
}
ViewModel が必要な場合は、Fragment を追加することもできます。