Kotlin中的lateinit、lazy

发布时间 2023-05-26 12:37:46作者: jqc

lateinit

lateinit 关键字用来修饰一个类的非空成员变量,表示该成员变量的值在稍后合适的时机会初始化,例如:

class Test {
	lateinit var name: String
	
	fun test() {
		if (::name.isInitialized) {
			println("name is initialized")
		}
		println(name)
	}
}

在给 lateinit 修饰的成员变量赋值之前如果有代码试图访问该成员变量的值,则会直接抛出异常。在访问 lateinit 修饰的成员变量之前可以先用 isInitialized 来判断该成员变量是否已经赋值了。

上述代码转成字节码之后再反编译后如下:

public final class Test {
   public String name;

   @NotNull
   public final String getName() {
      String var10000 = this.name;
      if (var10000 == null) {
         Intrinsics.throwUninitializedPropertyAccessException("name");
      }

      return var10000;
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      this.name = var1;
   }

   public final void test() {
      String var1;
      if (((Test)this).name != null) {
         var1 = "name is initialized";
         System.out.println(var1);
      }

      String var10000 = this.name;
      if (var10000 == null) {
         Intrinsics.throwUninitializedPropertyAccessException("name");
      }

      var1 = var10000;
      System.out.println(var1);
   }
}

从上述代码可以看出,编译器会为 lateinit 修饰的成员变量name生成get方法和set方法,在调用get方法的时候会判断变量是否已经初始化,若未初始化则会抛出 UninitializedPropertyAccessException

isInitializedkotlin.reflect.KProperty0的一个扩展属性,可以用于判断某个成员变量是否已经初始化。

lazy

lazy 用于延迟初始化一个成员变量到其首次被访问的时候,且该成员变量只会被初始化一次,例如:

class Test {

    val name: String by lazy {
        "william"
    }
	
    fun test() {
        println(name)
    }
}

lazy 需要配合by关键字来使用,表示将 name 的初始化由lazy代理完成。因为只会被初始化一次,所以 lazy 只能用于 val 修饰的成员变量。

lazy 是一个高阶函数,其具体实现在LazyJVM.kt中:


public interface Lazy<out T> {
    /**
     * Gets the lazily initialized value of the current Lazy instance.
     * Once the value was initialized it must not change during the rest of lifetime of this Lazy instance.
     */
    public val value: T

    /**
     * Returns `true` if a value for this Lazy instance has been already initialized, and `false` otherwise.
     * Once this function has returned `true` it stays `true` for the rest of lifetime of this Lazy instance.
     */
    public fun isInitialized(): Boolean
}

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

其接受一个初始化函数作为参数,返回一个SynchronizedLazyImpl对象,该对象实现了Lazy接口。SynchronizedLazyImpl 从其名字上就可以看出它使用synchronized关键字确保了其初始化变量的过程是线程安全的。

如果我们能够确认成员变量的初始化一定是线程安全的,那么可以使用另一个lazy方法来指定LazyThreadSafeMode,如下所示:

public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
    when (mode) {
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }
	
class Test {

    val name: String by lazy (LazyThreadSafeMode.NONE) {
        "william"
    }
	
    fun test() {
        println(name)
    }
}

查看SafePublicationLazyImpl的实现可以发现其为成员变量加了Volatile关键字修饰,可以保证可见性。而UnsafeLazyImpl的实现则没有任何同步措施,需要确保每次访问成员变量都是在同一个线程。