【问题标题】:Access property delegate in Kotlin在 Kotlin 中访问属性委托
【发布时间】:2015-09-02 23:13:29
【问题描述】:

Kotlin 具有委托属性,这是一个非常好的特性。但有时get()set() 方法还不够。假设我想懒惰地创建一个Closeable 对象并稍后关闭它。以下是如何实现此类委托属性的示例:

fun <T : Closeable> closeableLazy(initializer: () -> T) =
        CloseableLazyVal(initializer)

class CloseableLazyVal<T : Closeable>(
    private val initializer: () -> T
) : ReadOnlyProperty<Any?, T> {

    private var value: T? = null

    override fun get(thisRef: Any?, desc: PropertyMetadata): T {
        if (value == null) {
            value = initializer()
        }
        return value
    }

    fun close() {
        value?.close()
    }
}

这就是我想使用它的方式:

private val stream by closeableLazy { FileOutputStream("/path/to/file") }

fun writeBytes(bytes: ByteArray) {
    stream.write(bytes)
}

override fun close() {
    stream::delegate.close() // This line will not compile
}

不幸的是,这种方法不起作用,因为 Kotlin 似乎不允许直接访问属性委托。有什么办法可以做我想做的事吗?或者是否有任何计划将此类功能添加到 Kotlin,因为它会是一个非常简洁的功能。

【问题讨论】:

  • 顺便说一句,你不需要调用 let。只需使用 value?.close()
  • @cypressious,你是对的,谢谢。

标签: properties delegates kotlin


【解决方案1】:

好的,所以我想出了以下解决方案:

fun <T : Closeable> closeableLazy(initializer: () -> T) =
        CloseableLazyVal(initializer)

class CloseableLazyVal<T : Closeable>(
        private val initializer: () -> T
) : ReadOnlyProperty<CloseableDelegateHost, T> {

    private var value: T? = null

    override fun get(thisRef: CloseableDelegateHost, desc: PropertyMetadata): T {
        if (value == null) {
            value = initializer()
            thisRef.registerCloseable(value!!)
        }
        return value!!
    }

}

interface CloseableDelegateHost : Closeable {
    fun registerCloseable(prop : Closeable)
}

class ClosableDelegateHostImpl : CloseableDelegateHost {

    val closeables = arrayListOf<Closeable>()

    override fun registerCloseable(prop: Closeable) {
        closeables.add(prop)
    }

    override fun close() = closeables.forEach { it.close() }
}

class Foo : CloseableDelegateHost by ClosableDelegateHostImpl() {
    private val stream by closeableLazy { FileOutputStream("/path/to/file") }

    fun writeBytes(bytes: ByteArray) {
        stream.write(bytes)
    }

}

注意,该属性的 get 方法有一个参数 thisRef。我要求它继承自CloseableDelegateHost,它将在关闭时关闭任何已注册的Closeables。为了简化实现,我将此接口委托给一个简单的基于列表的实现。

更新(复制自 cmets): 我意识到,您可以将委托声明为单独的属性,然后将第二个属性委托给它。这样您就可以轻松访问委托本身。

private val streamDelegate = closeableLazy { FileOutputStream("/path/to/file") }
private val stream by streamDelegate

fun writeBytes(bytes: ByteArray) {
    stream.write(bytes)
}

override fun close() {
    streamDelegate.close()
}

【讨论】:

  • 感谢您的回答。您的解决方案很好,可以解决这种特殊情况下的问题,但并非在所有情况下都有效。假设,例如,我想在不同的时刻关闭不同的 Closeable 对象。为了处理这种情况,我必须在你的实现中添加一些键来使close() 更加细化。在我看来,这种功能必须由语言本身提供,而不需要所有这些黑客攻击。
  • 我通常面临的另一个问题是当我需要检查Delegate.lazy 属性是否已初始化时。如果我们可以访问属性的委托,这个问题可以很容易地解决,但是所有的绕行似乎都很丑陋。
  • 我意识到,您可以将委托声明为单独的属性,然后将第二个属性委托给它。这样您就可以轻松访问委托本身。
  • 这是一个有趣的解决方法。在这种情况下,我将不得不在类中委派具有相同值的字段,因为这似乎是一个可行的解决方案。在将此类功能添加到语言核心之前,您的解决方案可能是实现我想要的最佳方式。
【解决方案2】:

在 Kotlin 1.1(自 beta 2 起)中,可以从属性中检索委托,因此您现在可以编写

override fun close() {
    (::stream.apply { isAccessible = true }.getDelegate() 
        as CloseableLazyVal<*>).close()
}

【讨论】:

  • 需要 kotlin 反射库
猜你喜欢
  • 1970-01-01
  • 2019-09-08
  • 1970-01-01
  • 2017-12-22
  • 2023-04-02
  • 1970-01-01
  • 2020-05-24
  • 2020-09-19
  • 2017-11-08
相关资源
最近更新 更多