在Kotlin中,如果你不想在构造函数内部或类主体顶部初始化一个类属性,你基本上有以下两个选项(来自语言引用):
延迟初始化
lazy()是一个接受lambda并返回lazy <T>实例的函数,它可以作为实现lazy属性的委托:第一次调用get()执行传递给lazy()的lambda并记住结果,后续调用get()只返回记住的结果。
例子
公共类Hello {
val myLazyString:通过lazy {"Hello"}
}
第一个调用和随后的调用,不管它在哪里,对myLazyString都会返回Hello
晚些时候初始化
Normally, properties declared as having a non-null type must be initialized in the constructor. However, fairly often this is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In this case, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.
To handle this case, you can mark the property with the lateinit modifier:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() { subject = TestSubject() }
@Test fun test() { subject.method() }
}
The modifier can only be used on var properties declared inside the body of a class (not in the primary constructor), and only when the property does not have a custom getter or setter. The type of the property must be non-null, and it must not be a primitive type.
那么,既然这两种方法都能解决同一个问题,如何在这两种方法中正确选择呢?
Lateinit vs lazy
lateinit
i)与可变变量[var]一起使用
lateinit var name: String //允许
lateinit val name: String //不允许
ii)只允许使用非空数据类型
lateinit var name: String //Allowed
lateinit var name: String? //Not Allowed
iii)这是对编译器的一个承诺,该值将在未来被初始化。
注意:如果你试图访问lateinit变量而没有初始化它,那么它会抛出UnInitializedPropertyAccessException异常。
懒惰的
i)延迟初始化是为了防止不必要的对象初始化。
ii)你的属性不会被初始化,除非你使用它。
iii)只初始化一次。下次使用它时,将从缓存中获取值。
iv)它是线程安全的(它在第一次使用的线程中初始化。其他线程使用缓存中存储的相同值)。
v)该属性只能为val。
vi)属性可以是任何类型(包括基本类型和空值,这是lateinit不允许的)。
下面是lateinit var和by lazy{…}委托的属性:
lazy { ... } delegate can only be used for val properties, whereas lateinit can only be applied to vars, because it can't be compiled to a final field, thus no immutability can be guaranteed;
lateinit var has a backing field which stores the value, and by lazy { ... } creates a delegate object in which the value is stored once calculated, stores the reference to the delegate instance in the class object and generates the getter for the property that works with the delegate instance. So if you need the backing field present in the class, use lateinit;
In addition to vals, lateinit cannot be used for nullable properties or Java primitive types (this is because of null used for uninitialized value);
lateinit var can be initialized from anywhere the object is seen from, e.g. from inside a framework code, and multiple initialization scenarios are possible for different objects of a single class. by lazy { ... }, in turn, defines the only initializer for the property, which can be altered only by overriding the property in a subclass. If you want your property to be initialized from outside in a way probably unknown beforehand, use lateinit.
Initialization by lazy { ... } is thread-safe by default and guarantees that the initializer is invoked at most once (but this can be altered by using another lazy overload). In the case of lateinit var, it's up to the user's code to initialize the property correctly in multi-threaded environments.
A Lazy instance can be saved, passed around and even used for multiple properties. On contrary, lateinit vars do not store any additional runtime state (only null in the field for uninitialized value).
If you hold a reference to an instance of Lazy, isInitialized() allows you to check whether it has already been initialized (and you can obtain such instance with reflection from a delegated property). To check whether a lateinit property has been initialized, you can use property::isInitialized since Kotlin 1.2.
A lambda passed to by lazy { ... } may capture references from the context where it is used into its closure.. It will then store the references and release them only once the property has been initialized. This may lead to object hierarchies, such as Android activities, not being released for too long (or ever, if the property remains accessible and is never accessed), so you should be careful about what you use inside the initializer lambda.
此外,问题中还没有提到另一种方法:delegates.notull(),它适用于延迟初始化非空属性,包括Java原语类型的属性。