在Kotlin中访问属性委托

15

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 中,因为它将是一个非常好的特性。


1
顺便说一下,你不需要调用let。只需使用value?.close()即可。 - Kirill Rakhman
@cypressious,你说的对,谢谢。 - Michael
2个回答

8

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

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,当它关闭时,将关闭任何注册的Closeable。为了简化实现,我将此接口委托给基于列表的简单实现。 更新(来自评论):我意识到,您可以将代理声明为单独的属性,然后将第二个属性委托给它。这样,您可以轻松访问代理本身。
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()
}

1
谢谢您的回答。您的解决方案很好,可以解决这个特定情况下的问题,但并不适用于所有情况。比如说,如果我想在不同的时刻关闭不同的Closeable对象,那么就需要在您的实现中添加一些键来使close()更加细粒度化。我认为这种功能应该由语言本身提供,而不是通过各种hack来实现。 - Michael
1
我通常遇到的另一个问题是需要检查 Delegate.lazy 属性是否已初始化。如果我们可以访问属性的委托,这个问题就很容易解决了,但所有的解决方法似乎都很丑陋。 - Michael
2
我意识到可以将委托声明为单独的属性,然后将第二个属性委托给它。这样,您可以轻松访问委托本身。 - Kirill Rakhman
这是一个有趣的解决方法。在这种情况下,我将不得不委托具有相同值的类中的字段,但它似乎是一个可行的解决方案。在此功能添加到语言核心之前,您的解决方案可能是实现我想要的最佳方式。 - Michael

8
在 Kotlin 1.1 (自 beta 2 版本开始),可以从属性中检索委托,因此现在可以编写以下代码:
override fun close() {
    (::stream.apply { isAccessible = true }.getDelegate() 
        as CloseableLazyVal<*>).close()
}

1
需要使用 Kotlin Reflect 库。 - Louis CAD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接