匿名 Kotlin 侦听器中未解析的引用

Unresolved reference inside anonymous Kotlin listener

我有下面的代码。它是科特林。知道为什么 textToSpeech.setLanguage(Locale.UK) 中的 textToSpeech 告诉我们没有为 textToSpeech 解析的引用吗?

val textToSpeech = TextToSpeech(
            applicationContext,
            object : TextToSpeech.OnInitListener {
                override fun onInit(status: Int) {
                    if (status == TextToSpeech.SUCCESS) {
                        textToSpeech.setLanguage(Locale.UK)
                    }
                }

            })

一开始以为是Idea kotlin plugin的bug,结果好像真的编译不了

Kotlin 强化了变量初始化策略,现在禁止在其初始化器中引用变量,即使在 lambda 和对象表达式中,这似乎是合理的:想象一下在变量赋值之前立即调用 lambda。

对于你的情况,我可以建议在这个相当麻烦的结构中使用 object expression 作为解决方法:

val textToSpeech = object {
    val value: TextToSpeech get() = inner
    private val inner = TextToSpeech(
            applicationContext,
            { value.setLanguage(Locale.UK) }
    )
}.value

这将通过value 属性 初始化一个内部带有inner 的匿名对象。请注意,inner 初始化程序使用 value 属性。然后value提取出来就可以使用了

但是请记住这个技巧是不安全的:在运行时,在分配 inner 之前使用 value(例如在 TextToSpeech 构造函数中)将抛出 NullPointerException .

此外,我已将 OnInitListener 替换为使用 SAM conversion 的 lambda 来缩短,但仍然可以在那里使用对象表达式。


UPD: 检查 this question 我为推广这种方法所做的努力。使用它,你可以写

val textToSpeech = selfReference {
    TextToSpeech(
        applicationContext,
        { self.setLanguage(Locale.UK) }
    )
}

参见sources on Github

这是解决该问题的一种非常易读且清晰的方法。首先你应该定义这个:

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
}

以你的问题为例,你可以这样做:

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

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