Kotlin "assign value exactly once on the first call" 怎么表达?

How to express in Kotlin "assign value exactly once on the first call"?

寻找一种自然的 Kotlin 方式让 startTime 仅在特定位置初始化一次。

以下简单的实现有两个问题:

  1. 它不是线程安全的
  2. 它没有表达“变量已经或将在 Item 实例的生命周期中恰好分配一次”这一事实

class Item {
    var startTime: Instant?

    fun start(){
        if (startTime == null){
            startTime = Instant.now()
        }

        // do stuff
    }
}

我相信某种委托可以适用于此。换句话说,这段代码需要类似于 lazy 变量的东西,但在第一次读取时没有初始化,而是仅在显式调用“touching”方法后才会发生。也许 Wrap 调用可以给出可能实现的想法。

class Wrap<T>(
  supp: () -> T
){
   private var value: T? = null
   private val lock = ReentrantLock()
  
   fun get(){
     return value
   }

   fun touch(){
      lock.lock()

      try{
          if (value == null){
              value = supp()
          } else {
              throw IllegalStateExecption("Duplicate init")
          }
      } finally{
        lock.unlock()
      }
   }
}

合并AtomicReference.compareAndSet with a custom backing field怎么样?

您可以使用私有 setter 并确保 class 设置值的唯一位置来自 start() 方法。

class Item(val value: Int) {
    private val _startTime = AtomicReference(Instant.EPOCH)
    var startTime: Instant?
        get() = _startTime.get().takeIf { it != Instant.EPOCH }
        private set(value) = check(_startTime.compareAndSet(Instant.EPOCH, value)) { "Duplicate set" }

    fun start() {
        startTime = Instant.now()
    }

    override fun toString() = "$value: $startTime"
}

fun main() = runBlocking {
    val item1 = Item(1)
    val item2 = Item(2)
    println(Instant.now())
    launch { println(item1); item1.start(); println(item1) }
    launch { println(item1) }
    delay(1000)
    println(item2)
    item2.start()
    println(item2)
    println(item2)
    item2.start()
}

示例输出:

2021-07-14T08:20:27.546821Z
1: null
1: 2021-07-14T08:20:27.607365Z
1: 2021-07-14T08:20:27.607365Z
2: null
2: 2021-07-14T08:20:28.584114Z
2: 2021-07-14T08:20:28.584114Z
Exception in thread "main" java.lang.IllegalStateException: Duplicate set

我认为您的 Wrap class 是实现此目的的良好起点。我肯定会把它设为 属性 委托,并且 touch() 可以大大简化:

fun touch() {
    synchronized(this) {
        check(value == null) { "Duplicate init" }
        value = supp()
    }
}

然后你可以移除lock。但总的来说,这是一个很好的方法。

如果你想重用 stdlib 中的 lazy util,那么你可以通过用另一个对象包装它来做到这一点,该对象在被询问之前不会读取它的值:

class ManualLazy<T : Any>(private val lazy: Lazy<T>) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
        return if (lazy.isInitialized()) lazy.value else null
    }

    fun touch() {
        lazy.value
    }
}

class Item {
    private val _startTime = ManualLazy(lazy { Instant.now() })
    val startTime: Instant? by _startTime

    fun start(){
        _startTime.touch()
    }
}

当然,根据您的需要,您可以使用类似的技术以完全不同的方式实现它。

这可能被认为是利用或入侵 lazy util。我同意并且我认为 Wrap 方法更好。