Kotlin延迟属性和值重置:可重置的惰性委托

时间:2016-03-02 16:15:20

标签: android properties kotlin lazy-initialization

所以我使用kotlin用于安卓,在浏览视图时,我倾向于执行以下操作:

private val recyclerView by lazy { find<RecyclerView>(R.id.recyclerView) }

此方法可行。但是,有一种情况会导致应用程序出错。如果这是一个片段,并且片段进入后台,则将再次调用onCreateView,并重新创建片段的视图层次结构。这意味着,懒惰启动的recyclerView将指出不再存在的旧视图。

解决方案是这样的:

private lateinit var recyclerView: RecyclerView

并初始化onCreateView内的所有属性。

我的问题是,有没有办法重置懒惰属性,以便可以再次初始化它们?我喜欢这样的事实:初始化都是在类的顶部完成的,有助于保持代码的有序性。具体问题可以在这个问题中找到:kotlin android fragment empty recycler view after back

5 个答案:

答案 0 :(得分:24)

这是一个可重置懒人的快速版本,它可能更优雅,需要双重检查线程安全,但这基本上是这个想法。您需要管理(跟踪)延迟委托的内容,以便您可以调用重置,然后可以管理和重置。这将lazy()包装在这些管理类中。

以下是您的最终课程的样子,例如:

class Something {
    val lazyMgr = resettableManager()
    val prop1: String by resettableLazy(lazyMgr) { ... }
    val prop2: String by resettableLazy(lazyMgr) { ... }
    val prop3: String by resettableLazy(lazyMgr) { ... }
}

然后,为了让懒惰的所有内容在下次访问时返回到新值:

lazyMgr.reset() // prop1, prop2, and prop3 all will do new lazy values on next access

重置懒惰的实现:

class ResettableLazyManager {
    // we synchronize to make sure the timing of a reset() call and new inits do not collide
    val managedDelegates = LinkedList<Resettable>()

    fun register(managed: Resettable) {
        synchronized (managedDelegates) {
            managedDelegates.add(managed)
        }
    }

    fun reset() {
        synchronized (managedDelegates) {
            managedDelegates.forEach { it.reset() }
            managedDelegates.clear()
        }
    }
}

interface Resettable {
    fun reset()
}

class ResettableLazy<PROPTYPE>(val manager: ResettableLazyManager, val init: ()->PROPTYPE): Resettable {
    @Volatile var lazyHolder = makeInitBlock()

    operator fun getValue(thisRef: Any?, property: KProperty<*>): PROPTYPE {
        return lazyHolder.value
    }

    override fun reset() {
        lazyHolder = makeInitBlock()
    }

    fun makeInitBlock(): Lazy<PROPTYPE> {
        return lazy {
            manager.register(this)
            init()
        }
    }
}

fun <PROPTYPE> resettableLazy(manager: ResettableLazyManager, init: ()->PROPTYPE): ResettableLazy<PROPTYPE> {
    return ResettableLazy(manager, init)
}

fun resettableManager(): ResettableLazyManager = ResettableLazyManager()

有些单元测试可以肯定:

class Tester {
   @Test fun testResetableLazy() {
       class Something {
           var seed = 1
           val lazyMgr = resettableManager()
           val x: String by resettableLazy(lazyMgr) { "x ${seed}" }
           val y: String by resettableLazy(lazyMgr) { "y ${seed}" }
           val z: String by resettableLazy(lazyMgr) { "z $x $y"}
       }

       val s = Something()
       val x1 = s.x
       val y1 = s.y
       val z1 = s.z

       assertEquals(x1, s.x)
       assertEquals(y1, s.y)
       assertEquals(z1, s.z)

       s.seed++ // without reset nothing should change

       assertTrue(x1 === s.x)
       assertTrue(y1 === s.y)
       assertTrue(z1 === s.z)

       s.lazyMgr.reset()

       s.seed++ // because of reset the values should change

       val x2 = s.x
       val y2 = s.y
       val z2 = s.z

       assertEquals(x2, s.x)
       assertEquals(y2, s.y)
       assertEquals(z2, s.z)

       assertNotEquals(x1, x2)
       assertNotEquals(y1, y2)
       assertNotEquals(z1, z2)

       s.seed++ // but without reset, nothing should change

       assertTrue(x2 === s.x)
       assertTrue(y2 === s.y)
       assertTrue(z2 === s.z)
   }
}

答案 1 :(得分:1)

我有同样的任务,这就是我用过的东西:

import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

class SingletonLazy<T : Any>(val initBlock: () -> T, val clazz: Class<T>) {
    operator fun <R> provideDelegate(ref: R, prop: KProperty<*>): ReadOnlyProperty<R, T> = delegate()

    @Suppress("UNCHECKED_CAST")
    private fun <R> delegate(): ReadOnlyProperty<R, T> = object : ReadOnlyProperty<R, T> {
        override fun getValue(thisRef: R, property: KProperty<*>): T {
            val hash = clazz.hashCode()
            val cached = singletonsCache[hash]
            if (cached != null && cached.javaClass == clazz) return cached as T
            return initBlock().apply { singletonsCache[hash] = this }
        }
    }
}

private val singletonsCache = HashMap<Int, Any>()

fun <T> clearSingleton(clazz: Class<T>) : Boolean {
    val hash = clazz.hashCode()
    val result = singletonsCache[hash]
    if (result?.javaClass != clazz) return false

    singletonsCache.remove(hash)
    return true
}

inline fun <reified T : Any> singletonLazy(noinline block: () -> T): SingletonLazy<T>
        = SingletonLazy(block, T::class.java)

用法:

val cat: Cat by singletonLazy { Cat() }

fun main(args: Array<String>) {
    cat
    println(clearSingleton(Cat::class.java))
    cat // cat will be created one more time
    println(singletonsCache.size)
}

class Cat {
    init { println("creating cat") }
}

当然,您可能拥有自己的缓存策略。

答案 2 :(得分:1)

我找到了一种方便的方法:

import java.util.concurrent.atomic.AtomicReference
import kotlin.reflect.KProperty

fun <T> resetableLazy(initializer: () -> T) = ResetableDelegate(initializer)

class ResetableDelegate<T>(private val initializer: () -> T) {
    private val lazyRef: AtomicReference<Lazy<T>> = AtomicReference(
        lazy(
            initializer
        )
    )

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return lazyRef.get().getValue(thisRef, property)
    }

    fun reset() {
        lazyRef.set(lazy(initializer))
    }
}

测试:

import org.junit.Assert
import org.junit.Test

class ResetableLazyData {
    var changedData = 0
    val delegate = resetableLazy { changedData }
    val readOnlyData by delegate
}

class ResetableLazyTest {

    @Test
    fun testResetableLazy() {
        val data = ResetableLazyData()
        data.changedData = 1
        Assert.assertEquals(data.changedData, data.readOnlyData)
        data.changedData = 2
        Assert.assertNotEquals(data.changedData, data.readOnlyData)
        data.delegate.reset()
        Assert.assertEquals(data.changedData, data.readOnlyData)
        data.changedData = 3
        Assert.assertNotEquals(data.changedData, data.readOnlyData)
    }
}

答案 3 :(得分:0)

如果您想要非常简单的内容,扩展Lazy<T>并且在几行代码中仍然有效,则可以使用

class MutableLazy<T>(private val initializer: () -> T) : Lazy<T> {
    private var cached: T? = null
    override val value: T
        get() {
            if (cached.isNull()) {
                cached = initializer()
            }
            @Suppress("UNCHECKED_CAST")
            return cached as T
        }

    fun reset() {
        cached = null
    }

    override fun isInitialized(): Boolean = cached != null

    companion object {
        fun <T> resettableLazy(value: () -> T) = MutableLazy(value)
    }
}

像这样使用它:

class MainActivity() {
    val recyclerViewLazy = MutableLazy.resettable {
        findViewById<RecyclerView>(R.id.recyclerView)
    }
    val recyclerView by recyclerViewLazy
    // And later on
    override onCreate(savedInstanceState: Bundle?) {
        recyclerViewLazy.reset() /** On next get of the recyclerView, it would be updated*/
    }
}

部分借用

lazy( LazyThreadSafetyMode.NONE ) { }

stlib中提供的

答案 4 :(得分:0)

您可以尝试

fun <P, T> renewableLazy(initializer: (P) -> T): ReadWriteProperty<P, T> =
    RenewableSynchronizedLazyWithThisImpl({ t, _ ->
        initializer.invoke(t)
    })

fun <P, T> renewableLazy(initializer: (P, KProperty<*>) -> T): ReadWriteProperty<P, T> =
    RenewableSynchronizedLazyWithThisImpl(initializer)

    class RenewableSynchronizedLazyWithThisImpl<in T, V>(
        val initializer: (T, KProperty<*>) -> V,
        private val lock: Any = {}
    ) : ReadWriteProperty<T, V> {

    @Volatile
    private var _value: Any? = null

    override fun getValue(thisRef: T, property: KProperty<*>): V {
        val _v1 = _value
        if (_v1 !== null) {
            @Suppress("UNCHECKED_CAST")
            return _v1 as V
        }

        return synchronized(lock) {
            val _v2 = _value
            if (_v2 !== null) {
                @Suppress("UNCHECKED_CAST") (_v2 as V)
            } else {
                val typedValue = initializer(thisRef, property)
                _value = typedValue
                typedValue
            }
        }
    }

    override fun setValue(thisRef: T, property: KProperty<*>, value: V) {
        // 不论设置何值,都会被重置为空
        synchronized(lock) {
            _value = null
        }
    }
    }