美文网首页Kotlin
Kotlin中lateinit和by lazy的区别

Kotlin中lateinit和by lazy的区别

作者: 就喊我哎吧 | 来源:发表于2020-07-25 22:24 被阅读0次

    区别

    从2者的概念上来区分,lateinit是修饰变量和属性,by lazy 是2个单词组成,其中by 是关键字,lazy 是一个函数。

    先看看下面的代码,就会很清楚的理解2者之间的区别了。

    lateinit

    由于kotlin有严格的语法要求变量需要声明是否可以为null,但由于在实际的业务场景中,这个变量必须在某些时候才能做初始化操作,并且这个变量肯定不为null,如果为null,就是逻辑有问题了。这个时候可以使用lateinit来修饰这个变量。如果没有初始化就使用这个变量,那么就会抛出异常。

    lateinit的使用

    class LateInitExample {
        lateinit var value:String
    }
    
    fun main() {
        val example = LateInitExample()
        // 如果没有赋值就使用,直接抛出异常。
        example.value = "lateinit example"
        println("${example.value}")
    }
    

    lateinit的具体实现

    lateinit可以达到这样的效果,其实非常简单。类似于如下的java代码。

    public class LateInit {
    
        private String value;
    
        public String getValue() {
            // 如果没有初始化过,就抛出异常
            if (value == null){
                throw new RuntimeException("lateinit property value has not been initialized");
            }
            return value;
        }
    
        public void setValue(String value) {
            // 这里要做非null检查
            this.value = value;
        }
    }
    

    by lazy

    前面说了,by 和 lazy要单独拿出来看,不能当做一个整体来看。

    by:这里涉及到了kotlin的委托中委托属性
    lazy:一个kotlin的函数

    by和委托属性

    先简单看一下如果实现委托属性。

    import kotlin.reflect.KProperty
    
    class DelegateExample {
        var name:String by Delegate()
    }
    
    class Delegate{
    
        private var _name:String = "default value provide by Delegate"
    
        operator fun getValue(example: DelegateExample, property: KProperty<*>): String {
            println("Delegate : get Value")
            return _name
        }
    
        operator fun setValue(example: DelegateExample, property: KProperty<*>, s: String) {
            println("Delegate : set Value: $s")
            _name = s
        }
    }
    
    fun main() {
        val example = DelegateExample();
        println(example.name)
        example.name = "tom"
        println(example.name)
    }
    

    输出结果:

    Delegate : get Value
    default value provide by Delegate
    Delegate : set Value: tom
    Delegate : get Value
    tom
    
    

    简单通俗理解就是这个变量的get,set都是委托给了另外一个类来去操作。
    如果是var变量,必须要有getValue和setValue2个方法,val变量不需要setValue方法。
    语法是: val/var <属性名>: <类型> by <表达式>

    Kotlin 标准库为几种有用的委托提供了工厂方法,延迟属性 Lazy就是其中之一。

    lazy

    已经知道了by来干什么的,具体怎么用by,我们先来看看by lazy如何使用。

    class ByLazyExample {
        val name:String by lazy {
            println("get name by lazy")
            "tom"
        }
    }
    
    fun main() {
        val example = ByLazyExample()
        println(example.name)
        println(example.name)
        println(example.name)
    }
    

    输出结果:

    get name by lazy
    tom
    tom
    tom
    

    我们发现get name by lazy只执行了一次,只有第一次取得时候执行了lazy的代码块。所以by lazy可以做到延迟初始化,等需要的时候再去初始化,并且只会执行一次代码块。

    模仿一下lazy的实现

    代码块这里是使用了lambda表达式。我们先尝试着根据我们刚才学习的by自己实现一个可以做到上面输出。在上面的DelegateExample中我们已经使用了by,但是我们的get会执行多次,我们试着更新一下上面的代码,模仿一下lazy。

    import kotlin.reflect.KProperty
    
    class DelegateExample {
        val name:String by delegate {
            println("get name by lazy")
            "tom"
        }
    }
    
    fun delegate(init : () -> String):Delegate{
        return Delegate(init)
    }
    
    
    class Delegate{
    
        private val _init : () -> String
        constructor(init : () -> String){
            _init = init
        }
        private var _name:String = "default value provide by Delegate"
        private var _nameIsLoad:Boolean = false;
    
        operator fun getValue(example: DelegateExample, property: KProperty<*>): String {
            if (_nameIsLoad){
                return _name;
            }
            _name = _init()
            _nameIsLoad = true
            return _name
        }
    }
    
    fun main() {
        val example = DelegateExample();
        println(example.name)
        println(example.name)
        println(example.name)
    }
    

    输出结果:

    get name by lazy
    tom
    tom
    tom
    

    这样看着我们写的by delegate和by lazy很像啊。实际上,这个和by lazy的实现真的很像,只是很粗糙。

    我们先看一下我们更新了DelegateExample哪些东西,

    • 首先定义了一个函数,返回值是Delegate
    • Delegate接收一个函数类型参数,新增了一个Boolean值来判断是否已经赋值过了。
    • getValue的时候判断如果没有赋值,就通过传递进来的函数赋值,初始化。
    • DelegateExample的name变量由var变成了val,这个很好理解,因为既然name的赋值都交给我们自己的lambda来做了,那么setValue就不需要了。这也是为什么kotlin的by lazy不支持var的原因,因为没有了setValue方法实现。

    lazy的实现

    根据上面我们写的代码,应该基本上对lazy的实现已经清楚了。接下来看一下kotlin是如果实现lazy的。
    下面贴出部分代码。

    
    // 跟我们刚才写的很像,有个方法,参数是一个lambda表达式,返回一个Lazy对象
    public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
    
    // Lazy接口
    public interface Lazy<out T> {   
        public val value: T
    }
    
    // Lazy的实现类
    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
        
        private val lock = lock ?: this
        
        // 采用double check的方式处理value的获取。保证多线程情况下代码块也只会被执行一次。
        override val value: T
            get() {
                val _v1 = _value
                // 采用不是默认值就证明赋值了,不是我们上面自己实现的Boolean值记录。
                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
                    }
                }
            }
    

    看到这里,相信大家只剩下一个疑惑了,为什么没有getValue方法,不是说by的实现需要getValue方法吗?这里,kotlin使用了扩展函数来做。

    
    // 这里返回value,就会执行实现类的override value get了。
    public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
    

    总结

    看完上面的代码,相信已经把lateinit和by lazy解释清楚了。

    • 2者的概念不同,一个是延迟初始化,一个是委托属性。
    • by lazy只能用在val声明的变量上,为什么上面代码也解释了,并且是线程安全的。

    初学,有不对的地方欢迎大家指出,互相学习。

    相关文章

      网友评论

        本文标题:Kotlin中lateinit和by lazy的区别

        本文链接:https://www.haomeiwen.com/subject/ipnmlktx.html