首页 > 解决方案 > 如何在 Kotlin 中表达“在第一次调用时只赋值一次”?

问题描述

寻找一种自然的 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()
      }
   }
}

标签: kotlindesign-patternsdelegateslazy-evaluation

解决方案


AtomicReference.compareAndSet与自定义支持字段结合如何?

您可以使用私有设置器并确保类设置值的唯一位置来自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

推荐阅读