编写Kotlin util函数,在初始化程序中提供自引用

时间:2016-01-30 10:04:02

标签: initialization this lazy-evaluation kotlin

我试图将我的黑客从另一个question的答案中推广出来。

它应该提供一种方法来引用一个尚未在其初始化器中构造的值(当然,不是直接,而是在lambdas和对象表达式中)。

我现在所拥有的:

class SelfReference<T>(val initializer: SelfReference<T>.() -> T) {
    val self: T by lazy {
        inner ?: throw IllegalStateException("Do not use `self` until initialized.")
    }

    private val inner = initializer()
}

fun <T> selfReference(initializer: SelfReference<T>.() -> T): T {
    return SelfReference(initializer).self
}

它有效,请看这个例子:

class Holder(var x: Int = 0,
             val action: () -> Unit)

val h: Holder = selfReference { Holder(0) { self.x++ } }
h.action()
h.action()
println(h.x) //2

但此时initializer引用构造值的方式是self属性。

我的问题是:有没有办法重写SelfReference以便initializer传递参数(或接收者)而不是self属性?这个问题可以重新表述为:有没有办法将一个延迟评估的接收器/参数传递给函数或以某种方式实现这种语义?

改善代码的其他方法有哪些?

<小时/> UPD:一种可能的方法是传递一个返回self的函数,因此它将在it()内用作initializer。还在寻找其他的。

3 个答案:

答案 0 :(得分:1)

在完全通用的情况下,我设法做到的最好的是:

class SelfReference<T>(val initializer: SelfReference<T>.() -> T)  {
    val self: T by lazy {
        inner ?: throw IllegalStateException("Do not use `self` until initialized.")
    }

    private val inner = initializer()
    operator fun invoke(): T = self
}

添加invoke操作符后,您可以通过以下方式使用它:

val h: Holder = selfReference { Holder(0) { this().x++ } }

这是我使它看起来像您“正常”编写的东西的最接近的地方。

可悲的是,我认为不可能完全摆脱对该元素的显式访问。既然这样做,您将需要一个T.() -> T类型的lambda参数,但是如果没有T的实例并且成为T的泛型,您将无法调用该参数,这是没有干净安全的方法的获取该实例。

但是也许我错了,这可以帮助您考虑解决问题的方法

答案 1 :(得分:0)

  

有没有办法重写SelfReference,以便初始化器传递参数(或接收器)而不是使用自身属性?这个问题可以重新表述为:有没有办法将一个延迟评估的接收器/参数传递给函数或以某种方式实现这种语义?

我不确定我是否完全理解您的用例,但这可能是您正在寻找的内容:

fun initHolder(x: Int = 0, holderAction: Holder.() -> Unit) : Holder {
    var h: Holder? = null
    h = Holder(x) { h!!.holderAction() }
    return h
}

val h: Holder = initHolder(0) { x++ }
h.action()
h.action()
println(h.x) // 2

这是有效的,因为holderAction是一个带接收器(Holder.() -> Unit)的lambda,它给予接收者成员lambda访问权。

这是一般解决方案,因为您可能无法更改相应Holder构造函数的签名。值得注意的是,此解决方案不需要打开类,否则可以使用辅助构造函数对子类进行类似的方法。

当只有少数类需要更改时,我更喜欢这个解决方案来创建SelfReference类。

您可能需要检查null而不是使用!!才能发出有用的错误。如果Holder在其构造函数或init块中调用action,则会得到空指针异常。

答案 2 :(得分:0)

我很确定您可以使用以下类似方式以更加可读和清晰的方式获得相同的结果:

fun <T> selfReferenced(initializer: () -> T) = initializer.invoke()
operator fun<T> T.getValue(any: Any?, property: KProperty<*>) = this

稍后使用

val valueName: ValueType by selfReferenced{
    //here you can create and use the valueName object
}

以引用的问题https://stackoverflow.com/a/35050722/2196460为例,您可以这样做:

val textToSpeech:TextToSpeech by selfReferenced {
TextToSpeech(
        App.instance,
        TextToSpeech.OnInitListener { status ->
            if (status == TextToSpeech.SUCCESS) {
                textToSpeech.setLanguage(Locale.UK)
            }
        })
    }

在selfReferenced块内,您可以不受限制地使用外部对象。您唯一需要注意的是明确声明类型,以避免递归类型检查问题。