Kotlin:延迟到val,或者是一个可以设置一次的var

Man*_*eko 18 android immutability lazy-evaluation kotlin kotlin-lateinit

只是好奇:在Kotlin中,我很想得到一些可以通过懒惰来初始化的val,但是有一个参数.那是因为我需要为了初始化它而创建的东西很晚.

具体来说,我希望我有:

private lateinit val controlObj:SomeView
Run Code Online (Sandbox Code Playgroud)

要么:

private val controlObj:SomeView by lazy { view:View->view.findViewById(...)}
Run Code Online (Sandbox Code Playgroud)

然后:

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


    controlObj = view.findViewById(...)
Run Code Online (Sandbox Code Playgroud)

或在第二种情况下controlObj.initWith(view)或类似的情况:

return view
Run Code Online (Sandbox Code Playgroud)

我无法使用,by lazy因为by lazy初始化时不会接受外部参数.在这个例子中 - 包含view.

当然我有,lateinit var但如果我能确保它在设置后变为只读,我会在一行中完成它会很好.

是否有一种非常干净的方法来创建只读初始化一次的只读变量,但只有当其他变量出生时?任何init once关键字?在init之后,编译器知道它是不可变的?

我知道这里存在潜在的并发问题,但如果我敢于在init之前访问它,我当然应该被抛出.

hlu*_*kyi 14

您可以像这样实现自己的委托:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

之后,您可以按以下方式使用它:

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
     }

}
Run Code Online (Sandbox Code Playgroud)

如果您在初始化之前尝试访问值,则会出现异常以及尝试重新分配新值时。


Mar*_*nik 6

在此解决方案中,您实现了一个自定义委托,它成为您类中的一个单独属性。委托有一个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")
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,如果您需要线程安全,则解决方案略有不同:

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")
}
Run Code Online (Sandbox Code Playgroud)


Sta*_*dar 5

您可以使用lazy。例如与TextView

    val text by lazy<TextView?>{view?.findViewById(R.id.text_view)}
Run Code Online (Sandbox Code Playgroud)

哪里。viewgetView()之后onCreateView()您可以用作text只读变量

  • 不过有趣的是......解决了一些用例。view实际上是一个方法,getView()。它是在**调用**时评估的。 (2认同)