在 Kotlin 中,如果您不想在构造函数内或类主体顶部初始化类属性,则基本上有以下两个选项(来自语言参考):
lazy()是一个函数,它接受一个 lambda 并返回一个实例,Lazy<T>该实例可以作为实现惰性属性的委托:第一次调用get()执行传递给的 lambdalazy()并记住结果,随后的调用get()只是返回记住的结果。 例子 public class Hello { val myLazyString: String by lazy { "Hello" } }
lazy()是一个函数,它接受一个 lambda 并返回一个实例,Lazy<T>该实例可以作为实现惰性属性的委托:第一次调用get()执行传递给的 lambdalazy()并记住结果,随后的调用get()只是返回记住的结果。
lazy()
Lazy<T>
get()
例子
public class Hello { val myLazyString: String by lazy { "Hello" } }
因此,第一次调用和后续调用,无论它在哪里,myLazyString都会返回Hello
myLazyString
Hello
通常,声明为非空类型的属性必须在构造函数中初始化。但是,这通常并不方便。例如,可以通过依赖注入来初始化属性,或者在单元测试的 setup 方法中初始化属性。在这种情况下,您不能在构造函数中提供非 null 初始化程序,但您仍然希望在引用类主体内的属性时避免 null 检查。 要处理这种情况,您可以使用 lateinit 修饰符标记属性: public class MyTest { lateinit var subject: TestSubject @SetUp fun setup() { subject = TestSubject() } @Test fun test() { subject.method() } } 修饰符只能用于在类主体内(不能在主构造函数中)声明的 var 属性,并且只能在属性没有自定义 getter 或 setter 时使用。属性的类型必须是非空的,并且不能是原始类型。
通常,声明为非空类型的属性必须在构造函数中初始化。但是,这通常并不方便。例如,可以通过依赖注入来初始化属性,或者在单元测试的 setup 方法中初始化属性。在这种情况下,您不能在构造函数中提供非 null 初始化程序,但您仍然希望在引用类主体内的属性时避免 null 检查。
要处理这种情况,您可以使用 lateinit 修饰符标记属性:
public class MyTest { lateinit var subject: TestSubject @SetUp fun setup() { subject = TestSubject() } @Test fun test() { subject.method() } }
修饰符只能用于在类主体内(不能在主构造函数中)声明的 var 属性,并且只能在属性没有自定义 getter 或 setter 时使用。属性的类型必须是非空的,并且不能是原始类型。
那么,既然它们都可以解决同一个问题,那么如何在这两个选项之间做出正确的选择呢?
lateinit var以下是委托属性和委托属性之间的显着差异by lazy { ... }:
lateinit var
by lazy { ... }
lazy { ... }delegate 只能用于val属性,而lateinit只能应用于vars,因为它不能编译为final字段,因此不能保证不变性;
lazy { ... }
val
lateinit
var
final
lateinit var有一个存储值的支持字段,并by lazy { ... }创建一个委托对象,一旦计算出值就存储在其中,将对委托实例的引用存储在类对象中,并为与委托实例一起工作的属性生成 getter。因此,如果您需要类中存在的支持字段,请使用lateinit;
除了vals,lateinit不能用于可空属性或 Java 原始类型(这是因为null用于未初始化的值);
null
lateinit var可以从可以看到对象的任何地方初始化,例如从框架代码内部,并且单个类的不同对象可以有多个初始化场景。by lazy { ... }反过来,定义了属性的唯一初始化程序,只能通过覆盖子类中的属性来更改它。如果您希望您的属性以事先可能未知的方式从外部初始化,请使用lateinit.
初始化by lazy { ... }默认是线程安全的,并保证初始化器最多被调用一次(但这可以通过使用另一个lazy重载来改变)。在 的情况下lateinit var,由用户代码在多线程环境中正确初始化属性。
lazy
一个Lazy实例可以被保存、传递,甚至可以用于多个属性。相反,lateinit vars 不存储任何额外的运行时状态(仅null在未初始化值的字段中)。
Lazy
如果您持有对 的实例的引用Lazy,则isInitialized()允许您检查它是否已经被初始化(并且您可以通过委托属性的反射获取此类实例)。要检查一个 lateinit 属性是否已经被初始化,你可以使用property::isInitializedKotlin 1.2 起。
isInitialized()
property::isInitialized
传递给的 lambdaby lazy { ... }可以从使用它的上下文中捕获引用到它的闭包中。然后它将存储引用并仅在属性初始化后才释放它们。这可能会导致对象层次结构(例如 Android 活动)不会被释放太久(或者永远不会被释放,如果该属性仍然可以访问并且永远不会被访问),因此您应该小心在初始化程序 lambda 中使用的内容。
另外,问题中没有提到另一种方法:Delegates.notNull(),它适用于非空属性的延迟初始化,包括Java原始类型的属性。
Delegates.notNull()