访问Android Sensor的延迟非可空属性时的空属性

时间:2018-05-04 11:30:55

标签: android kotlin kotlin-android-extensions

val初始化Sensorlazy时,编译器会忽略返回的值可以是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} } Warning message

Sensor?分配给nullable val

不应该引发编译时错误 < / p>

Resolved

当访问值non-null并且它为null时,它会按预期产生运行时异常proximitySensor

Kotlin标准库参考中的

'java.lang.String android.hardware.Sensor.getName()' on a null object reference如下:

  1. lazy()返回存储lambda初始化程序的lazy()实例。
  2. getter的第一次调用执行传递给Lazy<T>和的lambda 存储其结果。
  3. 随后,getter执行返回存储的值。
  4.   

    分配时不应抛出异常,而不是   当我们试图访问它时?

    lazy()

2 个答案:

答案 0 :(得分:2)

是否存在编译时错误?

  

将nullable分配给非null的val时,不应该引发编译时错误吗?

是的,但这不是这里发生的事情在这种情况下,您要为您的值分配platform type并告诉编译器将其视为不可为空。应该有IllegalStateException的运行时错误 - 正如@MarkoTopolnik指出的那样,这是一个错误

错误

KT-8135涵盖了此错误。这是与代表KT-24258直接相关的链接问题。 还有一个讨论主题,其中包含一些可运行的示例here

何时抛出运行时异常?

当您以需要非可空值的方式使用proximitySensor时,将触发此错误,如下所示:

val s: Sensor = proximitySensor
proximitySensor.someMethod()
println(proximitySensor.someProperty)

但是当初始化属性时,这不会抛出异常!我们可以解决这个问题......

为什么首次初始化Lazy属性时没有错误?

由于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) }

一旦访问了惰性值,就会抛出异常。

其他选项

将proximitySensor声明为可为空的

警告正在指引您正确的方向,或者不检查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委托的本质是将初始化推迟到最新的可能时间,这是第一个属性访问。正确的实现将确保在非可空属性的评估完成之前抛出异常。不幸的是,目前的实施不正确。