Kotlin里的空安全,几种情况 lateinit / by lazy /(!!.) / (?:)

首先看几段代码

代码段一

fun String.i(TAG: String = ">>>>") {
    if (BuildConfig.DEBUG)
        Log.i(TAG, this)
}

代码段二

fun String.e(TAG: String?) {
    if (BuildConfig.DEBUG)
        Log.i(TAG ?: ">>>>", this)
}

代码段三

fun String.d(TAG: String? = ">>>>") {
    if (BuildConfig.DEBUG)
        Log.d(TAG ?: ">>>>", this)
}

首先这三段代码如果使用不规范, 都会在编译器报错, 不会在你运行时,报空指针异常
代码一: 调用方法时: 1.允许我们不传值,因为方法参数有默认值 2.不能传空值,因为Kotlin的空安全机制
代码二: 调用方法时: 1 必须传值, 2.可以允许传空值,因为类型名称后面加问号,表示该对象可以为空
代码三: 调用方法时: 1. 可以不用传值, 2 可以传任何值,包括空

Kotlin的空安全是 语言级别的支持

    fun dome1() {
        var str: String = null
        var l = str.length // 编译期直接报错
    }

此时报错直接在 null 位置, 因为kotlin不允许赋值为null

Kotlin 允许变量为空

如果变量 str 就是无法保证为空, 例如数据来自网络请求的数据,难免不为空.
我们要在定义变量时, 在类型后面添加一个 问号,这样就可以解除非空的限制

    fun dome1() {
        var str: String? = null
        var l = str.length // 编译期直接报错
    }

此时null 不报错了, 但是第二行还是报错,

Kotlin 允许空对象去调用方法的

因为想要使用一个可能为空的对象时, 需要用空安全方法去调用 (?. )

    fun dome1() {
        var str: String? = null
        "str:$str".i()
        var l = str?.length
        "l : ${l.toString()}".i()
    }

此时终于不报错了, 那我们运行下,并打印这两个值,

Kotlin 里空对象调用方法,返回值还是空

会发现,运行没有错, 也就是说空的str调用方法时, 没有报空指针异常, 那 str?.length 运行后的结果是什么呢

2021-09-28 16:46:32.218 6302-6302/com.zhf.wiki I/>>>>: str:null
2021-09-28 16:46:32.218 6302-6302/com.zhf.wiki I/>>>>: l : null

没错, Kotlin 里(?.)的意思就是,如果str非空,就返回str.length,否则返回null。
如果最后返回为null的话,我们还可以进行一步的处理。

Kotlin 里使用Elvis操作符(?😃 可以给空值赋值一个默认值

如一下代码, 我们在str 为空时将长度返回值设置成 (-1)

    fun dome1() {
        var str: String? = null
        "str:$str".i()
        var l = str?.length
        "l : ${l.toString()}".i()
        var l2 = str?.length ?: -1
        "l2 : ${l2.toString()}".i()
    }
2021-09-28 16:55:26.692 12257-12257/com.zhf.wiki I/>>>>: str:null
2021-09-28 16:55:26.692 12257-12257/com.zhf.wiki I/>>>>: l : null
2021-09-28 16:55:26.692 12257-12257/com.zhf.wiki I/>>>>: l2 : -1

Kotlin 也会报空针异常 (!!.)

(!!.)操作符 ,是非空断言运算符,若该值为空则抛出异常

    fun dome1() {
        var str: String? = null
        "str:$str".i()
        var l = str?.length
        "l : ${l.toString()}".i()
        var l2 = str?.length ?: 0
        "l : ${l2.toString()}".i()

        var l3 = str!!.length
        "l : ${l3.toString()}".i()

    }

2021-09-28 17:03:00.332 16613-16613/com.zhf.wiki I/>>>>: str:null
2021-09-28 17:03:00.332 16613-16613/com.zhf.wiki I/>>>>: l : null
2021-09-28 17:03:00.332 16613-16613/com.zhf.wiki I/>>>>: l : 0
...
 Process: com.zhf.wiki, PID: 16613
    java.lang.RuntimeException: kotlin.KotlinNullPointerException
    ....

Kotlin 里肯定不为空的情况, 怎么写 ( lateinit )

  1. 一种方法就是定义一个可为空的变量, 使用 (!!.)操作符,调用
  2. 另一种就是 lateinit 使用关键字,慎用! 一定要保证它肯定在使用前被初始化, 例如例如在Android开发的时候初始化一个控件的时候,findViewById()是需要在onCreate之后就调用的
  3. 基本类型的局部变量不允许使用“lateinit”修饰符
  4. lateinit 只能用在var变量上
  5. 允许编译器识别非null属性的值未存储在构造函数阶段中以进行正常编译 .
    fun dome1() {
        lateinit var str: String
        "str:$str".i()
        var l = str?.length
        "l : ${l.toString()}".i()
    }
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property str has not been initialized

当我们把str 用 lateinit关键字修饰的时候, 用(?.) 方法调用,发现 编译期没有报错 ,但运行还是报错了, 所以lateinit 修饰的变量和null时有区别的. 大家可以自行研究下
基本类型的局部变量不允许使用“lateinit”修饰符
在这里插入图片描述

安全的类型转换(as? )

如果对象不是目标类型,那么常规类型转换可能会导致 ClassCastException。 另一个选择是使用安全的类型转换,如果尝试转换不成功则返回 null:

可控类型的集合

如果你有一个可空类型元素的集合,并且想要过滤非空元素,你可以使用 filterNotNull 来实现:

	val nullableList: List<Int?> = listOf(1, 2, null, 4)
	val intList: List<Int> = nullableList.filterNotNull()

by Lazy惰性初始化

lazy()是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托:
第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。
也就是说只有第一次调用该属性的时候,委托方法才会被执行。并且只能修饰val的属性。

    private val textView by bindView<TextView>(R.id.textView)
    
    private val Activity.viewFind:Activity.(Int)->View?
        get()={
            findViewById(it)
        }
     fun <V:View> Activity.bindView(id:Int):Lazy<V> = lazy{
        viewFind(id) as V
    }
  1. 只能用在val变量上
  2. 当属性用到的时候才会初始化”lazy{}”里面的内容
  3. 而且再次调用属性的时候,只会得到结果,而不会再次执行lazy{}的运行过程
  4. 如果你想要线程安全,使用 blockingLazy(): 它还是按照同样的方式工作,但保证了它的值只会在一个线程中计算,并且所有的线程都获取的同一个值。
val str: String by lazy {
    println("getLazy")
    "123"
}

感谢:
https://www.jianshu.com/p/d2491bbf8150
https://blog.csdn.net/guyue35/article/details/93601139

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值