在val
初始化Sensor
类lazy
时,编译器会忽略返回的值可以是null
注意:val
被声明为 Sensor
,而不是 Sensor?
private val sensorManager by lazy { getSystemService(Context.SENSOR_SERVICE) as SensorManager }
private val proximitySensor: Sensor by lazy { sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY) }
sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)
也可以返回null
,并且该方法未使用@Nullable
注释。因此,无法在编译时检查它。
此外,当检查空时, Android Studio 会发出警告,因为我们尚未将其声明为{{1} }
Sensor?
分配给nullable
val
时 不应该引发编译时错误 < / p>
当访问值non-null
并且它为null时,它会按预期产生运行时异常。
proximitySensor
'java.lang.String android.hardware.Sensor.getName()' on a null object reference
如下:
lazy()
返回存储lambda初始化程序的lazy()
实例。Lazy<T>
和的lambda
存储其结果。分配时不应抛出异常,而不是 当我们试图访问它时?
lazy()
答案 0 :(得分:2)
将nullable分配给非null的val时,不应该引发编译时错误吗?
是的,但这不是这里发生的事情在这种情况下,您要为您的值分配platform type并告诉编译器将其视为不可为空。应该有IllegalStateException
的运行时错误 - 正如@MarkoTopolnik指出的那样,这是一个错误。
KT-8135涵盖了此错误。这是与代表KT-24258直接相关的链接问题。 还有一个讨论主题,其中包含一些可运行的示例here。
当您以需要非可空值的方式使用proximitySensor
时,将触发此错误,如下所示:
val s: Sensor = proximitySensor
proximitySensor.someMethod()
println(proximitySensor.someProperty)
但是当初始化属性时,这不会抛出异常!我们可以解决这个问题......
由于sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)
在java中实现,因此返回platform type。因此,Lazy委托的类型为Lazy<Sensor!>
。
Lazy类运行初始化程序lambda。保存lambda的结果,然后返回T
类型,即Sensor!
。
如果我们在调用lazy
函数时明确声明类型参数:
private val proximitySensor: Sensor by lazy<Sensor> { sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY) }
一旦访问了惰性值,就会抛出异常。
警告正在指引您正确的方向,或者不检查null或将其声明为可空。通过明确地将类型声明为可为空来告诉编译器应该将值视为可为空:
private val proximitySensor: Sensor? by lazy { sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY) }
正如您所提到的,sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)
可能会返回null。我们可以返回一个默认值:
val proximitySensor: Sensor by lazy {
sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY) ?: SOME_DEFAULT_VALUE
}
Kotlin中的平台类型通过允许调用者确定类型是可空的还是不可空的,使得使用Java更加实用。假设Java中的所有内容都可以为空的替代方法意味着即使Java代码永远不会返回null,您也必须进行大量痛苦的空检查。
*注意:如果您正在编写Java代码,则可以使用@Nullable
和@NonNull
注释向Kotlin编译器以及其他静态分析工具提供元数据。诸如Spring Framework have done this to their APIs之类的库。
这是Kotlin开发人员Andrey Breslav的a talk,它深入讨论了平台类型的互操作性设计决策。
答案 1 :(得分:1)
感谢您提出的问题,现在是confirmed bug in Kotlin compiler's JVM backend。
我用你的问题做了一个MCVE:
id
这是支持Java代码:
val sensorManager = SensorManager()
val proximitySensor: Sensor by lazy { sensorManager.sensor }
fun main(args: Array<String>) {
println(proximitySensor)
}
然后......猜猜怎么着?它打印public class SensorManager {
public Sensor getSensor() {
return null;
}
}
public class Sensor {}
。我称之为“Kotlin bug”:)
你问:
分配时不应抛出异常,而不是在我们尝试访问它时抛出异常吗?
对于null
委托,当您第一次访问该属性时,唯一的分配就会发生。这传播到lazy
getter,它包含众所周知的双重检查的lazy init惯用语,它是你提供的初始化程序块运行的唯一时间。
即使没有详细的讨论,也应该清楚SynchronizedLazyImpl.value
委托的本质是将初始化推迟到最新的可能时间,这是第一个属性访问。正确的实现将确保在非可空属性的评估完成之前抛出异常。不幸的是,目前的实施不正确。