有没有一种优雅的kotlin方法可以让编译器相信,我刚刚为其分配了一个实值的可为null的字段可以';不再为nul



我已经读过了!!一般应避免。有没有一种方法可以以更优雅的方式编写以下代码,而不必添加过时的空检查、重复或死代码块之类的内容?

class A(var field: Thing?) {
fun getField(): Thing {
if (field == null) {
field = Thing()
}
return field!!
}
}

此外,我不明白编译器为什么需要!!-'祈祷在这种情况下,这不是一个完全的操作员。

编辑:考虑一下,如果字段为null,那么潜在的解决方案使用延迟初始化对我来说很重要!

问题

正如Enzokie在评论中已经提到的那样,在null检查之后,另一个线程可能已经更改了字段。编译器无法知道这一点,所以你必须告诉它

class A(var field: Thing?) {
fun getField(): Thing {
if (field == null) {
field = Thing()
}
// another thread could have assigned null to field
return field!! // tell the compiler: I am sure that did not happen
}
}

解决方案(渴望)

在您的特定情况下,最好在构造函数中使用参数f(您也可以将其命名为"字段",但为了清楚起见,我避免了这样做)(没有val/var),然后将其分配给属性field,您可以将fThing的新实例分配给该属性。

这可以用Elvis算子:?非常简洁地表达,如果不为空,则取表达式的左手边,否则取表达式的右手边。因此,在结束字段中的类型将是Thing

class A(f: Thing?) {
val field = f ?: Thing() // inferred type Thing
}

解决方案(懒惰)

由于gids已经提到了这一点,如果您需要懒散地初始化字段,您可以使用委托的属性来这样做:

class A(f: Thing?) {
val field by lazy {
f ?: Thing() // inferred type Thing
}
}

呼叫站点不变:

val a = A(null) // field won't be initialized after this line...
a.field // ... but after this

这个怎么样?

class A(field: Thing?) {
private lateinit var field: Thing
init {
field?.let { this.field = it }
}
fun getField(): Thing {
if (!this::field.isInitialized) {
field = Thing()
}
return field
}
}

定义字段时,实际上定义了一个变量和两个访问器方法:

val counter: Integer = 0

可以通过编写以下内容来自定义访问器方法:

val n = 0
val counter: Integer
get() = n++

这将在每次访问counter字段时执行n++,因此每次访问都会返回不同的值。这并不常见,也出乎意料,但在技术上是可能的。

因此,Kotlin编译器不能假设对同一字段的两次访问会两次返回相同的值。通常他们会这样做,但这并不能保证。

为了解决这个问题,您可以通过将字段复制到本地变量中来读取一次:

fun count() {
val counter = counter
println("The counter is $counter, and it is still $counter.")
}

最新更新