Kotlin

Kotlin中lateinit和by lazy的区别

2020-07-25  本文已影响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哪些东西,

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解释清楚了。


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

上一篇下一篇

猜你喜欢

热点阅读