我想在普通JVM中使用类似Android Compose的remember(key) { init }的东西Kotlin。因此,某种类型的Lazy委托,但在每次访问时,它都会调用一个键函数,如果其返回值更改,则会重新计算该值。是否有一个简单的标准库功能来实现这一点,或者我需要创建一个自定义类?
remember(key) { init }
rm5edbpk1#
首先,没有简单直接的方法来解决这个问题。问题是,我们无法观察到一个常规属性或函数的值,所以我们不知道什么时候重新计算值。根据我们的需求,有多种可能的解决方案。一种是提供一种方法来使缓存的值无效,因此创建一个与Lazy非常相似的实用程序,但具有额外的invalidate()功能。另一个解决方案是你建议的:指定某种密钥,用于确定是否需要重新计算。获取当前键的代码应该非常轻量级,因为每次访问属性时都会调用它。示例实施方式:
Lazy
invalidate()
fun <K, V> recalculatingLazy(keyProvider: () -> K, valueProvider: (K) -> V) = object : ReadOnlyDelegate<V> { private var lastKey: Any? = NotSet @Suppress("UNCHECKED_CAST") private var lastValue = null as V override fun getValue(thisRef: Any?, property: KProperty<*>): V { val key = keyProvider() return if (key == lastKey) { lastValue } else { lastKey = key valueProvider(key).also { lastValue = it } } } } private object NotSet interface ReadOnlyDelegate<T> { operator fun getValue(thisRef: Any?, property: KProperty<*>): T }
使用方法:
fun main() { var source = 1 val target by recalculatingLazy({ source }) { println("New key: $it, recalculating...") it * 2 } println(target) // recalculating... println(target) source = 2 println(target) // recalculating... println(target) }
请注意,此实现不是线程安全的。在实践中,这种解决方案是非常有限的,我们只能在非常特定的情况下使用它,因为我们需要一个关键字来识别更改。这类问题通常通过另一种解决方案来解决-通过创建可观察变量。我们有一个非常特定类型的源变量A,可以观察它的变化。然后目标变量B观察它,因此它知道何时重新计算。多年来,我们大大改进了这一概念:我们从一个简单的Observer Pattern开始,但是它有很多缺点。然后是Reactive Streams。Kotlin通过利用协程改进了这个概念-它提供了flows:
suspend fun main(): Unit = coroutineScope { val source = MutableStateFlow(1) val target = source.map { println("New key: $it, recalculating...") it * 2 } launch { target.collect { println("Value: $it") } } delay(500) source.value = 2 delay(500) source.value = 3 delay(500) }
请注意,这是完全不同的概念,一切都是异步工作的。我们可以重写这个例子,直接询问target变量,就像前面的例子一样,但是我们不能保证在设置了source之后,target什么时候会被更新。这是故意的行为。
target
source
nfeuvbwi2#
不,没有内置的方法来做到这一点。您必须做一些自定义的事情,尽管您可能会发现研究Lazy如何工作以自己复制它是有用的。
gmxoilav3#
lazy不能做到这一点,但任何简单的缓存应该能够做到。只需将其最大大小设置为1,从该高速缓存中获得的任何新密钥都将替换以前的值。下面是一个基于Caffeine缓存库的例子:
lazy
import com.github.benmanes.caffeine.cache.Caffeine class CachedValue<K, V>(loader: (K) -> V) { private val cache = Caffeine.newBuilder().maximumSize(1).build(loader) fun get(key: K): V = cache.get(key) }
3条答案
按热度按时间rm5edbpk1#
首先,没有简单直接的方法来解决这个问题。问题是,我们无法观察到一个常规属性或函数的值,所以我们不知道什么时候重新计算值。
根据我们的需求,有多种可能的解决方案。一种是提供一种方法来使缓存的值无效,因此创建一个与
Lazy
非常相似的实用程序,但具有额外的invalidate()
功能。另一个解决方案是你建议的:指定某种密钥,用于确定是否需要重新计算。获取当前键的代码应该非常轻量级,因为每次访问属性时都会调用它。示例实施方式:
使用方法:
请注意,此实现不是线程安全的。
在实践中,这种解决方案是非常有限的,我们只能在非常特定的情况下使用它,因为我们需要一个关键字来识别更改。这类问题通常通过另一种解决方案来解决-通过创建可观察变量。我们有一个非常特定类型的源变量A,可以观察它的变化。然后目标变量B观察它,因此它知道何时重新计算。
多年来,我们大大改进了这一概念:我们从一个简单的Observer Pattern开始,但是它有很多缺点。然后是Reactive Streams。Kotlin通过利用协程改进了这个概念-它提供了flows:
请注意,这是完全不同的概念,一切都是异步工作的。我们可以重写这个例子,直接询问
target
变量,就像前面的例子一样,但是我们不能保证在设置了source
之后,target
什么时候会被更新。这是故意的行为。nfeuvbwi2#
不,没有内置的方法来做到这一点。您必须做一些自定义的事情,尽管您可能会发现研究
Lazy
如何工作以自己复制它是有用的。gmxoilav3#
lazy
不能做到这一点,但任何简单的缓存应该能够做到。只需将其最大大小设置为1,从该高速缓存中获得的任何新密钥都将替换以前的值。下面是一个基于Caffeine缓存库的例子: