web-dev-qa-db-ja.com

Kotlin:lateinitからval、または代わりに1回設定できるvar

好奇心が強い:Kotlinでは、lazyで初期化できるvalを取得したいのですが、パラメーターが必要です。それは、初期化するために非常に遅く作成されたものが必要だからです。

特に:

_private lateinit val controlObj:SomeView
_

または:

_private val controlObj:SomeView by lazy { view:View->view.findViewById(...)}
_

その後:

_override fun onCreateView(....) {
    val view = inflate(....)


    controlObj = view.findViewById(...)
_

または、2番目の場合controlObj.initWith(view)またはそのようなもの:

_return view
_

_by lazy_は初期化時に使用される外部パラメーターを受け入れないため、_by lazy_を使用できません。この例では-を含むview

もちろん、_lateinit var_がありますが、設定後にのみ読み取り可能になり、1行で実行できることを確認できれば素晴らしいでしょう。

一度だけ初期化する読み取り専用変数を作成するかなりきれいな方法はありますか? _init once_キーワードはありますか?初期化後、コンパイラは不変であることを認識していますか?

ここで潜在的な同時実行性の問題を認識していますが、初期化する前に敢えてアクセスする場合は、必ずスローする価値があります

17
Maneki Neko

次のように独自のデリゲートを実装できます。

class InitOnceProperty<T> : ReadWriteProperty<Any, T> {

    private object EMPTY

    private var value: Any? = EMPTY

    override fun getValue(thisRef: Any, property: KProperty<*>): T {
        if (value == EMPTY) {
            throw IllegalStateException("Value isn't initialized")
        } else {
            return value as T
        }
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
        if (this.value != EMPTY) {
            throw IllegalStateException("Value is initialized")
        }
        this.value = value
    }
}

その後、次のように使用できます。

inline fun <reified T> initOnce(): ReadWriteProperty<Any, T> = InitOnceProperty()

class Test {

     var property: String by initOnce()

     fun readValueFailure() {
         val data = property //Value isn't initialized, exception is thrown
     }

     fun writeValueTwice() {
         property = "Test1" 
         property = "Test2" //Exception is thrown, value already initalized
     }

     fun readWriteCorrect() {
         property = "Test" 
         val data1 = property
         val data2 = property //Exception isn't thrown, everything is correct
     }

}

初期化される前に値にアクセスしようとすると、例外が発生します。また、新しい値を再割り当てしようとすると例外が発生します。

4
hluhovskyi

このソリューションでは、カスタムデリゲートを実装すると、クラスの個別のプロパティになります。デリゲートの内部にはvarがありますが、controlObjプロパティには必要な保証があります。

class X {
    private val initOnce = InitOnce<View>()
    private val controlObj: View by initOnce

    fun readWithoutInit() {
        println(controlObj)
    }

    fun readWithInit() {
        initOnce.initWith(createView())
        println(controlObj)
    }

    fun doubleInit() {
        initOnce.initWith(createView())
        initOnce.initWith(createView())
        println(controlObj)
    }
}

fun createView(): View = TODO()

class InitOnce<T : Any> {

    private var value: T? = null

    fun initWith(value: T) {
        if (this.value != null) {
            throw IllegalStateException("Already initialized")
        }
        this.value = value
    }

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
            value ?: throw IllegalStateException("Not initialized")
}

ところで、スレッドセーフが必要な場合、ソリューションはわずかに異なります。

class InitOnceThreadSafe<T : Any> {

    private val viewRef = AtomicReference<T>()

    fun initWith(value: T) {
        if (!viewRef.compareAndSet(null, value)) {
            throw IllegalStateException("Already initialized")
        }
    }

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
            viewRef.get() ?: throw IllegalStateException("Not initialized")
}
4
Marko Topolnik

次のように独自のデリゲートを実装できます。

private val maps = WeakHashMap<Any, MutableMap<String, Any>>()

object LateVal {
    fun bindValue(any: Any, propertyName: String, value: Any) {
        val map = maps.getOrPut(any) { mutableMapOf<String, Any>() }

        if (map[propertyName] != null) {
            throw RuntimeException("Value is initialized")
        }

        map[propertyName] = value
    }

    fun <T> lateValDelegate(): MyProperty<T> {
        return MyProperty<T>(maps)
    }

    class MyProperty<T>(private val maps: WeakHashMap<Any, MutableMap<String, Any>>) : ReadOnlyProperty<Any?, T> {

        override fun getValue(thisRef: Any?, property: KProperty<*>): T {
            val ret = maps[thisRef]?.get(property.name)
            return (ret as? T) ?: throw RuntimeException("Value isn't initialized")
        }
    }
}

fun <T> lateValDelegate(): LateVal.MyProperty<T> {
    return LateVal.MyProperty<T>(maps)
}

fun Any.bindValue(propertyName: String, value: Any) {
    LateVal.bindValue(this, propertyName, value)
}

その後、次のように使用できます。

class Hat(val name: String = "casquette") {
    override fun toString(): String {
        return name
    }
}

class Human {
    private val hat by lateValDelegate<Hat>()

    fun setHat(h: Hat) {
        this.bindValue(::hat.name, h)
    }

    fun printHat() {
        println(hat)
    }

}

fun main(args: Array<String>) {
    val human = Human()
    human.setHat(Hat())
    human.printHat()
}

初期化する前に値にアクセスしようとすると、新しい値を再割り当てしようとする場合と同様に例外が発生します。

また、DSLを記述して読みやすくすることもできます。

object to

infix fun Any.assigned(t: to) = this

infix fun Any.property(property: KProperty<*>) = Pair<Any, KProperty<*>>(this, property)

infix fun Pair<Any, KProperty<*>>.of(any: Any) = LateVal.bindValue(any, this.second.name, this.first)

そして、次のように呼び出します:

fun setHat(h: Hat) {
    h assigned to property ::hat of this
}
1
sunhang

lazyを使用できます。たとえば、TextView

_    val text by lazy<TextView?>{view?.findViewById(R.id.text_view)}
_

ここで、viewgetView()です。 onCreateView()の後、textを読み取り専用変数として使用できます

1

次のように独自のデリゲートを実装できます。

class LateInitVal {
    private val map: MutableMap<String, Any> = mutableMapOf()

    fun initValue(property: KProperty<*>, value: Any) {
        if (map.containsKey(property.name)) throw IllegalStateException("Value is initialized")

        map[property.name] = value
    }

    fun <T> delegate(): ReadOnlyProperty<Any, T> = MyDelegate()

    private inner class MyDelegate<T> : ReadOnlyProperty<Any, T> {

        override fun getValue(thisRef: Any, property: KProperty<*>): T {
            val any = map[property.name]
            return any as? T ?: throw IllegalStateException("Value isn't initialized")
        }

    }
}

その後、次のように使用できます。

class LateInitValTest {
    @Test
    fun testLateInit() {
        val myClass = MyClass()

        myClass.init("hello", 100)

        assertEquals("hello", myClass.text)
        assertEquals(100, myClass.num)
    }
}

class MyClass {
    private val lateInitVal = LateInitVal()
    val text: String by lateInitVal.delegate<String>()
    val num: Int by lateInitVal.delegate<Int>()

    fun init(argStr: String, argNum: Int) {
        (::text) init argStr
        (::num) init argNum
    }

    private infix fun KProperty<*>.init(value: Any) {
        lateInitVal.initValue(this, value)
    }
}

初期化する前に値にアクセスしようとすると、新しい値を再割り当てしようとする場合と同様に例外が発生します。

0
sunhang

Activityの場合、以下を実行してもかまいません。

private val textView: TextView by lazy { findViewById<TextView>(R.id.textView) }

Fragmentの場合、final型を使用してView変数を作成することは意味がありません。onDestroyViewの後にそのビューとの接続が失われるためです。

追伸 Kotlin合成プロパティ を使用して、ActivityおよびFragmentのビューにアクセスします。

一度だけ変数を設定したい場合は、シングルトンパターンを使用できます。

_companion object {
    @Volatile private var INSTANCE: SomeViewSingleton? = null

    fun getInstance(context: Context): SomeViewSingleton =
            INSTANCE ?: synchronized(this) {
                INSTANCE ?: buildSomeViewSingleton(context).also { INSTANCE = it }
            }

    private fun buildSomeViewSingleton(context: Context) =
            SomeViewSingleton(context)
}
_

そうすれば、getInstance(...)を呼び出すだけで、常に同じオブジェクトを取得できます。

オブジェクトのライフタイムを周囲のオブジェクトにバインドする場合は、コンパニオンオブジェクトをドロップして、クラスに初期化子を配置します。

また、同期ブロックは並行性の問題を処理します。

0
leonardkraemer