【问题标题】:Writing a Kotlin util function which provides self-reference in initializer编写一个在初始化程序中提供自引用的 Kotlin util 函数
【发布时间】:2016-01-30 10:04:01
【问题描述】:

我正在尝试将我的技巧从另一个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 的函数,因此它将在initializer 中用作it()。还在寻找其他的。

【问题讨论】:

标签: initialization this lazy-evaluation kotlin


【解决方案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
}

添加调用运算符让您可以通过以下方式使用它:

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

这是我让它看起来像你“通常”写的最接近的东西。

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

但也许我错了,这可以帮助你想出解决问题的办法

【讨论】:

    【解决方案2】:

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

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

    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.() -&gt; Unit) 的 lambda,它允许 lambda 访问接收器的成员。

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

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

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

    【讨论】:

      【解决方案3】:

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

      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 块中,您可以不受限制地使用外部对象。唯一需要注意的是显式声明类型以避免递归类型检查问题。

      【讨论】:

        猜你喜欢
        • 2015-07-16
        • 2019-10-24
        • 1970-01-01
        • 2018-03-12
        • 2021-07-08
        • 2021-03-13
        • 1970-01-01
        • 2023-03-18
        • 1970-01-01
        相关资源
        最近更新 更多