在Java中延迟初始化(lateinit)与空值初始化相比有何优势?

8

我在尝试一些Android相关的东西,同时学习Kotlin,我想知道如何初始化视图和属性。

据我所知,在Kotlin和Java中,合同(“在使用之前我会初始化”)以及UninitializedPropertyAccessException和NullPointerException两者都是更或多少等效的。你可以在这两种情况下进行isInitialized检查。我只是不知道,为什么JetBrains会如此关注null安全性,并以不同的形式引入完全相同的事物。

那么,lateinit有什么优势吗?

示例代码:

public class Foo {
    private String bar = null;

    public void bar123() {
        if (bar == null) {
            bar = "bar";
        }
    }
}

vs

class Foo {
    private lateinit var bar: String

    fun bar123() {
        if (!::bar.isInitialized) {
            bar = "bar"
        }
    }
}

1
优点在于,由于var bar声明为String而不是String?,因此您无需执行空值检查,因此您的代码(引用bar)更加简洁。 - Nikolai Shevchenko
1
但是在我看来,if (!::bar.isInitialized) 与空值检查基本相同。我看不出 Kotlin 版本更加简洁的地方。 - ssmid
1
@匿名者,我的意思是在初始化代码(构造函数/构建器等)之外进行额外的空值检查。这样可以避免在接收器期望String而你传递了String?时使用所有这些!! - Nikolai Shevchenko
1
@NikolaiShevchenko 好的,避免在类外使用 null 对我来说完全有意义。我试图返回一个未初始化的属性并得到了一个异常。你想把那个作为答案提交吗?然后我就可以标记问题已解决。 - ssmid
@Ano.Nymous,一旦初始化,就不能再将其设置为null。 - Animesh Sahu
3个回答

6

这个想法是让编译器知道该属性虽然稍后才会初始化,但其非空。这将减少接收代码中对此属性的null检查。

class Foo {
    lateinit var prop: String
}

class Bar {
    var prop: String? = null
}

fun consumeNotNull(arg: String) {
    println(arg)
}

fun main() {
    val foo = Foo()
    consumeNotNull(foo.prop) // OK

    val bar = Bar()
    consumeNotNull(bar.prop) // Error: Type mismatch: inferred type is String? but String was expected
    consumeNotNull(bar.prop!!) // OK
}

假设在N个地方引用了“bar.prop”,则每个地方都必须对其进行“大喊大叫”(bar.prop!!)才能使编译器满意。使用lateinit机制可以避免这种情况,让你的代码更加“安静”(并且保持代码更加清晰)。
当然,如果在运行时使用Foo::prop之前尚未初始化,将会抛出异常:
UninitializedPropertyAccessException: lateinit属性prop尚未初始化
与NullPointerException相比,它提供了更多描述信息。

1
lateinit 的唯一用途是这个吗? - pandey_shubham

3
除了 Nikolai Shevchenko 的回答之外:即使在类内部,我认为 `isInitialized` 是一个可为空属性更有用的指示器。
`lateinit` 的主要用例是当你不能在构造函数中初始化属性,但可以保证它在某种意义上“足够早”地初始化,以至于大多数使用不需要进行 `isInitialized` 检查。例如,因为某些框架调用一个方法立即在构造后初始化它。
事实上,最初没有 `isInitialized`;它只出现在 Kotlin 1.2 中,而 `lateinit` 已经在 1.0 中存在(我相信)。

2
另一个lateinit变量的用途是,一旦它被初始化,就无法将其取消初始化,“因此,一次检查将确保它永远不会成为null或被任何其他线程更改。”
class Foo {
    lateinit var prop: String
}

class Bar {
    var prop: String? = null
}

fun main() {
    val foo = Foo()
    foo.prop = "Hello"
    // You can never make it uninitialized now, you can only change it.
    // A single isInitialized is ok. (Rather than checking everytime, because it can be null again)

    val bar = Bar()
    bar.prop = "String"
    println(bar.prop!!)
    bar.prop = null
    println(bar.prop!!) // KotlinNullPointerException, check everytime you use it with ?. operator
    // Call when not null: bar.prop?.let { println(it) }
}

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