Kotlin "assign value exactly once on the first call" 怎么表达?
How to express in Kotlin "assign value exactly once on the first call"?
寻找一种自然的 Kotlin 方式让 startTime
仅在特定位置初始化一次。
以下简单的实现有两个问题:
- 它不是线程安全的
- 它没有表达“变量已经或将在 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
方法更好。
寻找一种自然的 Kotlin 方式让 startTime
仅在特定位置初始化一次。
以下简单的实现有两个问题:
- 它不是线程安全的
- 它没有表达“变量已经或将在 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
方法更好。