为什么Kotlin不允许在原始类型中使用lateinit?

70
在Kotlin语言中,默认情况下,我们必须在引入变量时对每个变量进行初始化。为了避免这种情况,可以使用lateinit关键字。在初始化之前引用lateinit变量会导致运行时异常。
然而,lateinit不能与原始类型一起使用。为什么呢?
3个回答

66
对于(不可为空的)对象类型,Kotlin使用“null”值来标记未初始化的“lateinit”属性,并在访问该属性时抛出适当的异常。
对于原始类型,没有这样的值,因此无法将属性标记为非初始化并提供“lateinit”需要提供的诊断信息。(我们可以尝试使用某种单独的标记,但是该标记在通过反射初始化字段时不会更新,这是“lateinit”的主要用例。)
因此,“lateinit”仅支持对象类型的属性。

53
为什么在运行时 lateinit Int 不能用能够保存空值的 Integer 类型来表示,而在未初始化时它是允许为空的?请在答案中进行澄清。 - Ilya
4
所以,我认为Kotlin将基本类型封装成对象(例如int到Int)。然而,为什么Int不能保持null,因为它是一个对象?另外,为什么我们不能声明一个可为空的参数并延迟初始化它?(var x:Int?) - Yao
2
@Yao,出于yole所解释的相同原因,你需要一个未使用的值来标记变量未初始化的事实。正如所解释的那样,对于基本类型,不存在这样的值(所有可以由基本类型变量持有的值都是有效的)。对于可空变量,同样存在这个问题:所有值,包括null,都是有效的(你可以用null初始化可空变量)。因此,没有未使用的值可以标记变量未初始化。 - Ekeko
3
但如果我说 lateinit var int: Int?,我不认为这会有问题,因为 Kotlin 已经用 Integer 来表示 Int? - forresthopkinsa
4
因为 lateinit? 是冲突的。前者表示一个变量在初始化后始终有某个值,而后者则表示一个变量可能有也可能没有某个值。在使用 lateinit 变量时,你不需要使用 ?. 或者检查是否为空值。 - Gustavo Maciel
显示剩余7条评论

9
简短的回答是,对于基本类型,您可以始终使用0作为默认值,对于可为空的类型,则可以使用null作为默认值。只有非空非原始类型可能需要使用lateinit来解决类型安全系统问题。
实际上,在Kotlin中,只要变量在第一次访问之前就有一个值,并且可以静态地证明,就不需要初始化变量。这意味着以下代码是完全有效的:
fun main(args: Array<String>) {
    var x: Int
    val y: Double

    x = 0
    y = x + 0.1

    println("$x, $y") 
}

但是也有(罕见的)情况,无法静态地证明初始化。最常见的情况是使用任何形式的依赖注入的类字段:

class Window {
    @Inject lateinit parent: Parent
}

1
我认为这有点误导人。它暗示了需要使用lateinit的唯一原因是在编译器无法确定属性是否已初始化时给出提示。 另一个完全有效的用例(对于基元类型尤其有用)是确保在客户端代码尝试在值被初始化/计算之前访问它时,会立即抛出描述性异常,而不是根据默认值0或false默默地执行错误操作。 - Mike Rippon
@MikeRippon,虽然这是使用它的一个可能原因,但这并不是Kotlin设计者的初衷。为了实现这个目的,他们提供了Delegates.notNull()。他们将lateinit保持在较低级别、高性能和与Java反射兼容的状态。拥有这两个不同的特性是必要的,这样我们就可以在通过Java库注入的类中实现非空性。 - Tenfour04

-3

我认为对于原始类型,将其初始化为0并在内存中保存简单值比存储有关对象可空性的额外信息更节省资源,这些信息是由lateinit机制使用的。

如果我说错了,请纠正我。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接