返回顶部
首页 > 资讯 > 后端开发 > Python >聊聊Kotlin 中 lateinit 和 lazy 的原理区别
  • 585
分享到

聊聊Kotlin 中 lateinit 和 lazy 的原理区别

2024-04-02 19:04:59 585人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

目录lateinit用法原理lazy用法原理the endreferences使用 Kotlin 进行开发,对于 latelinit 和 lazy 肯定不陌生。但其原理上的区别,可能

使用 Kotlin 进行开发,对于 latelinit 和 lazy 肯定不陌生。但其原理上的区别,可能鲜少了解过,借着本篇文章普及下这方面的知识。

lateinit

用法

非空类型可以使用 lateinit 关键字达到延迟初始化。

 class InitTest() {
     lateinit var name: String
 ​
     public fun checkName(): Boolean = name.isNotEmpty()
 }

如果在使用前没有初始化的话会发生如下 Exception。

 AndroidRuntime: FATAL EXCEPTION: main
      Caused by: kotlin.UninitializedPropertyAccessException: lateinit property name has not been initialized
         at com.example.tiramisu_demo.kotlin.InitTest.getName(InitTest.kt:4)
         at com.example.tiramisu_demo.kotlin.InitTest.checkName(InitTest.kt:10)
         at com.example.tiramisu_demo.MainActivity.testInit(MainActivity.kt:365)
         at com.example.tiramisu_demo.MainActivity.onButtonClick(MainActivity.kt:371)
         ...

为防止上述的 Exception,可以在使用前通过 ::xxx.isInitialized 进行判断。

 class InitTest() {
     lateinit var name: String
 ​
     fun checkName(): Boolean {
         return if (::name.isInitialized) {
             name.isNotEmpty()
         } else {
             false
         }
     }
 }
 Init: testInit():false

当 name 初始化过之后使用亦可正常。

 class InitTest() {
     lateinit var name: String
 ​
     fun injectName(name: String) {
         this.name = name
     }
 ​
     fun checkName(): Boolean {
         return if (::name.isInitialized) {
             name.isNotEmpty()
         } else {
             false
         }
     }
 }
 Init: testInit():true

原理

反编译之后可以看到该变量没有 @NotNull 注解,使用的时候要 check 是否为 null。

 public final class InitTest {
    public String name;
        
    @NotNull
    public final String getName() {
       String var10000 = this.name;
       if (var10000 == null) {
          Intrinsics.throwUninitializedPropertyAccessException("name");
       }
 ​
       return var10000;
    }
 ​
     public final boolean checkName() {
       String var10000 = this.name;
       if (var10000 == null) {
          Intrinsics.throwUninitializedPropertyAccessException("name");
       }
 ​
       CharSequence var1 = (CharSequence)var10000;
       return var1.length() > 0;
    }
 }

null 则抛出对应的 UninitializedPropertyAccessException。

 public class Intrinsics {
     public static void throwUninitializedPropertyAccessException(String propertyName) {
         throwUninitializedProperty("lateinit property " + propertyName + " has not been initialized");
     }
 ​
     public static void throwUninitializedProperty(String message) {
         throw sanitizeStackTrace(new UninitializedPropertyAccessException(message));
     }
 ​
     private static <T extends Throwable> T sanitizeStackTrace(T throwable) {
         return sanitizeStackTrace(throwable, Intrinsics.class.getName());
     }
 ​
     static <T extends Throwable> T sanitizeStackTrace(T throwable, String classNameToDrop) {
         StackTraceElement[] stackTrace = throwable.getStackTrace();
         int size = stackTrace.length;
 ​
         int lastIntrinsic = -1;
         for (int i = 0; i < size; i++) {
             if (classNameToDrop.equals(stackTrace[i].getClassName())) {
                 lastIntrinsic = i;
             }
         }
 ​
         StackTraceElement[] newStackTrace = Arrays.copyOfRange(stackTrace, lastIntrinsic + 1, size);
         throwable.setStackTrace(newStackTrace);
         return throwable;
     }
 }
 ​
 public actual class UninitializedPropertyAccessException : RuntimeException {
     ...
 }

如果是变量是不加 lateinit 的非空类型,定义的时候即需要初始化。

 class InitTest() {
     val name: String = "test"
 ​
     public fun checkName(): Boolean = name.isNotEmpty()
 }

在反编译之后发现变量多了 @NotNull 注解,可直接使用。

 public final class InitTest {
    @NotNull
    private String name = "test";
 ​
    @NotNull
    public final String getName() {
       return this.name;
    }
 ​
    public final boolean checkName() {
       CharSequence var1 = (CharSequence)this.name;
       return var1.length() > 0;
    }
 }

::xxx.isInitialized 的话进行反编译之后可以发现就是在使用前进行了 null 检查,为空直接执行预设逻辑,反之才进行变量的使用。

 public final class InitTest {
    public String name;
    ...
    public final boolean checkName() {
       boolean var2;
       if (((InitTest)this).name != null) {
          String var10000 = this.name;
          if (var10000 == null) {
             Intrinsics.throwUninitializedPropertyAccessException("name");
          }
 ​
          CharSequence var1 = (CharSequence)var10000;
          var2 = var1.length() > 0;
       } else {
          var2 = false;
       }
 ​
       return var2;
    }
 }

lazy

用法

lazy 的命名和 lateinit 类似,但使用场景不同。其是用于懒加载,即初始化方式已确定,只是在使用的时候执行。而且修饰的只是能是 val 常量。

 class InitTest {
     val name by lazy {
         "test"
     }
     
     public fun checkName(): Boolean = name.isNotEmpty()
 }

lazy 修饰的变量可以直接使用,不用担心 NPE。

 Init: testInit():true

原理

上述是 lazy 最常见的用法,反编译之后的代码如下:

 public final class InitTest {
    @NotNull
    private final Lazy name$delegate;
 ​
    @NotNull
    public final String getName() {
       Lazy var1 = this.name$delegate;
       return (String)var1.getValue();
    }
 ​
    public final boolean checkName() {
       CharSequence var1 = (CharSequence)this.getName();
       return var1.length() > 0;
    }
 ​
    public InitTest() {
       this.name$delegate = LazyKt.lazy((Function0)null.INSTANCE);
    }
 }

所属 class 创建实例的时候,实际分配给 lazy 变量的是 Lazy 接口类型,并非 T 类型,变量会在 Lazy 中以 value 暂存,当使用该变量的时候会获取 Lazy 的 value 属性。

Lazy 接口的默认 mode 是 LazyThreadSafetyMode.SYNCHRONIZED,其默认实现是 SynchronizedLazyImpl,该实现中 _value 属性为实际的值,用 volatile 修饰。

value 则通过 get() 从 _value 中读写,get() 将先检查 _value 是否尚未初始化

  • 已经初始化过的话,转换为 T 类型后返回

  • 反之,执行同步方法(默认情况下 lock 对象为 impl 实例),并再次检查是否已经初始化:

    • 已经初始化过的话,转换为 T 类型后返回
    • 反之,执行用于初始化的函数 initializer,其返回值存放在 _value 中,并返回
 public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
 ​
 private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
     private var initializer: (() -> T)? = initializer
     @Volatile private var _value: Any? = UNINITIALIZED_VALUE
     // final field is required to enable safe publication of constructed instance
     private val lock = lock ?: this
 ​
     override val value: T
         get() {
             val _v1 = _value
             if (_v1 !== UNINITIALIZED_VALUE) {
                 @Suppress("UNCHECKED_CAST")
                 return _v1 as T
             }
 ​
             return synchronized(lock) {
                 val _v2 = _value
                 if (_v2 !== UNINITIALIZED_VALUE) {
                     @Suppress("UNCHECKED_CAST") (_v2 as T)
                 } else {
                     val typedValue = initializer!!()
                     _value = typedValue
                     initializer = null
                     typedValue
                 }
             }
         }
 ​
     override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
 ​
     override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
 ​
     private fun writeReplace(): Any = InitializedLazyImpl(value)
 }

总之跟 Java 里双重检查懒汉模式获取单例的写法非常类似。

 public class Singleton {
     private static volatile Singleton singleton;
 ​
     private Singleton() {
     }
 ​
     public static Singleton getInstance() {
         if (singleton == null) {
             synchronized (Singleton.class) {
                 if (singleton == null) {
                     singleton = new Singleton();
                 }
             }
         }
         return singleton;
     }
 }

lazy 在上述默认的 SYNCHRONIZED mode 下还可以指定内部同步的 lock 对象。

     val name by lazy(lock) {
         "test"
     }

lazy 还可以指定其他 mode,比如 PUBLICATION,内部采用不同于 synchronizedCAS 机制。

     val name by lazy(LazyThreadSafetyMode.PUBLICATION) {
         "test"
     }

lazy 还可以指定 NONE mode,线程安全

     val name by lazy(LazyThreadSafetyMode.NONE) {
         "test"
     }

the end

lateinit 和 lazy 都是用于初始化场景,用法和原理有些区别,做个简单总结

lateinit 用作非空类型的初始化:

  • 在使用前需要初始化
  • 如果使用时没有初始化内部会抛出 UninitializedPropertyAccess Exception
  • 可配合 isInitialized 在使用前进行检查

lazy 用作变量的延迟初始化:

  • 定义的时候已经明确了 initializer 函数体
  • 使用的时候才进行初始化,内部默认通过同步和双重校验的方式返回持有的实例
  • 还支持设置 lock 对象和其他实现 mode

references

  • Kotlin-lateinit,::xxx.isInitialized的使用
  • Kotlin | 关于 Lazy ,你应该了解的这些事

到此这篇关于聊聊Kotlin 中 lateinit 和 lazy 的区别解析的文章就介绍到这了,更多相关Kotlin 中 lateinit 和 lazy区别内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 聊聊Kotlin 中 lateinit 和 lazy 的原理区别

本文链接: https://lsjlt.com/news/165249.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 聊聊Kotlin 中 lateinit 和 lazy 的原理区别
    目录lateinit用法原理lazy用法原理the endreferences使用 Kotlin 进行开发,对于 latelinit 和 lazy 肯定不陌生。但其原理上的区别,可能...
    99+
    2024-04-02
  • 聊聊Python中end=和sep=的区别
    end: 默认是换行,表示两个字符串最后以什么结尾。 eg: 换行 end="\n" sep: 默认是空格,表示两个字符串之间用什么分割。 eg: 空格 sep=" " 补充:python 中的 print(x, ...
    99+
    2022-06-02
    Python end= sep=
  • 聊聊Numpy.array中[:]和[::]的区别在哪
    [:]和[::]的区别蛮大的,用的好可以节省时间,下面以实例进行分析 array([:]) >>> import numpy as np >>>...
    99+
    2024-04-02
  • 聊聊git和github的区别
    Git是一种分布式版本控制系统,它能够记录代码的变化并帮助开发者管理版本的历史记录。Git的创建者是Linus Torvalds,它于2005年发布并在开源社区中推广,现在已成为开发者们最常用的版本控制工具之一。GitHub则是一个在线代码...
    99+
    2023-10-22
  • 聊聊SpringCloud和SpringCloudAlibaba的区别
    目录SpringCloud和SpringCloudAlibaba的区别SpringCloud Alibaba与Spring Cloud搭配方案开源地址SpringCloud几大痛点S...
    99+
    2024-04-02
  • 聊聊@RequestMapping和@GetMapping @PostMapping的区别
    @RequestMapping和@GetMapping @PostMapping的区别 最近学习看一些代码,发现对于发送请求这件事,有的地方用@RequestMapping,有的地方...
    99+
    2024-04-02
  • 聊聊BeanUtils.copyProperties和clone()方法的区别
    目录首先,BeanUtils有两种:效率:需要在pom文件中引入这个包在pom文件里面引入所需要的包新建一个实体类StudentEntity实现Cloneable接口测试方法最近撸代...
    99+
    2024-04-02
  • 聊聊JavaScript和php的区别有哪些
    JavaScript和PHP是两种不同的编程语言,它们各自有着自己的特点和用途。虽然它们都可以用于Web开发,但是它们的差异还是很大的。下面将详细介绍JavaScript和PHP之间的区别。语言类型JavaScript是一种客户端脚本语言,...
    99+
    2023-05-14
  • 聊聊python dropna()和notnull()的用法区别
    `dropna()`和`notnull()`是pandas库中用于处理缺失值的函数,它们的用法和功能有一些区别。`dropna()`...
    99+
    2023-08-16
    python
  • 聊聊码云和gitee的区别是什么
    码云和gitee是当前国内最受欢迎的两个代码托管平台,二者许多人会认为他们是一样的,但实际上,它们在某些功能和用法方面还是存在一些差异的。下面,我们就来探讨一下码云和gitee的几个不同之处。管理者背景首先,码云和gitee的背景不同。码云...
    99+
    2023-10-22
  • 聊聊python中not 与 is None的区别
    原因: list 获得的数据为空: 显示值为 [ ] 不同的判断--- is None ----not 两者结果不一样分析: 总之: not 判断的是内容,而is None则涉及到这个 list 或 dict...
    99+
    2022-06-02
    python not is None
  • 聊聊Java BigInteger里面的mod和remainder的区别
    目录BigInteger里的mod和remainder区别mod是模运算,remainder是求余运算BigInteger类的一些使用心得下面总结一下以后方便找1.给大数赋值2.把i...
    99+
    2024-04-02
  • 聊聊docker中容器与镜像的区别
    什么是镜像? 镜像可以看成是由多个镜像层叠加起来的一个文件系统(通过UnionFS与AUFS文件联合系统实现),镜像层也可以简单理解为一个基本的镜像,而每个镜像层之间通过指...
    99+
    2024-04-02
  • 详细聊聊TypeScript中unknown与any的区别
    目录前言1. unknown vs any2. unknown 和 any 的心智模式3.总结总结前言 我们知道 any 类型的变量可以被赋给任何值。 let myVar: a...
    99+
    2024-04-02
  • 聊聊Python 3 的字符串:str 和 bytes 的区别
    Python2的字符串有两种:str 和 unicode,Python3的字符串也有两种:str 和 bytes。Python2 的 str 相当于 Python3 的bytes,而unicode相当于Python3的str。Python2...
    99+
    2023-06-02
  • 聊聊ResourceBundle和properties读取配置文件的区别
    java.util.ResourceBundle 和java.util.properties 读取配置文件区别 这两个类都是读取properties格式的文件的,而Propertie...
    99+
    2024-04-02
  • 聊聊php换行和回车的区别是什么
    PHP中有一个非常常见的问题就是,如何换行和回车。虽然在很多情况下,“回车”和“换行”看起来一样,但它们的实际意义是有所不同的。在PHP中,换行和回车的区别非常重要,它们不仅仅可以影响代码的可读性,还可能会导致一些程序错误。下面,我们来看一...
    99+
    2023-05-14
  • 聊聊Gitee中Fork的具体操作和工作原理
    Gitee是一个国内广受欢迎的代码托管平台,由于其稳定性和优秀的社交化特性,越来越多的开发者选择在Gitee上托管自己的代码。而其中,Fork是Gitee上一个重要而实用的功能之一。本文将介绍Gitee中Fork的具体操作和工作原理,希望能...
    99+
    2023-10-22
  • 详细聊聊Vue中的MVVM模式原理
    目录1. MVVM模式2. Vue响应式3. Vue监听对象3.1 监听普通对象3.2 监听复杂对象(深度监听)4. Vue监听数组5. 使用 Object.defineProper...
    99+
    2023-03-03
    vue.js mvvm vue.js教程 vue mvvm模式
  • 聊一聊数据请求中Ajax、Fetch及Axios的区别
    目录Ajax介绍Fetch介绍Axios介绍对比 几种方式的对比总结Ajax介绍 Ajax是XMLHttpRequest 对象提供了对 HTTP 协议的完全的访问,早期的项...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作