Kotlin基础(二)空值处理

  • 许多编程语言(包括Java)中最常见的异常之一就是NullPointException(NPE),即访问空引用的成员会导致空引用异常
  • Kotlin在代码中消除了NullPointException,区分了一个引用为非空类型(non-nullable)和可空类型(nullable)

一、非空类型:non-nullable

1、变量默认是non-nullable类型的
2、non-nullable变量不能赋值为空,否者会编译报错
3、调用非空类型变量的属性或者方法,保证不会导致NPE,可以放心使用

var a = "danke"
a = null //编译报错:Null can not be a value of a non-null type String
println(a.length)

二、可空类型:nullable

(一)声明nullable的变量,需要使用?修饰

当某个变量的值可以为null的时候,必须在声明处的类型后添加?来标识该引用可为空。

1、申明可空的变量

var a:String? = "danke"
a = null //ok

2、申明函数返回值为可空的变量

fun parseInt(str: String): Int? {
    // ..
}

(二)无法直接使用nullable的属性或者方法

可空变量是不安全的,直接调用方法和属性,可能会引起NPE,所以编译器检测到了,那么编译失败

var a:String? = "danke"
println(a.length) // 编译失败,Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

(三)nullable变量无法直接赋值给non-nullable变量

可空变量无法直接赋值给非空变量,可能会引起NPE,所以编译器检测到了,那么编译失败Type mismatch

var a:String? = "danke"
var b = "danke"
b = a //编译失败 Type mismatch: inferred type is String? but String was expected

三、如何解决nullable的使用?

(一)在条件中检查null

1、显示检查变量是否为null,再进行逻辑处理。编译器会跟踪执行检查的信息,并允许在判空后(支持更复杂,更智能的条件),再调用变量的属性和方法

【变量处理】:

var a:String? = "danke"
if (a != null) {
    println(a.length)
}

【使用返回可空值的函数】:

fun parseInt(str: String): Int? {
    return str.toIntOrNull()
}

fun printProduct(arg1: String, arg2: String) {
    val x = parseInt(arg1)
    val y = parseInt(arg2)
    
    // 直接使用 [x * y] 会导致编译错误,因为他们可能为 null
    if (x != null && y != null) {
        // 在空检测后,x 与 y 会自动转换为非空值(non-nullable)
        println(x * y)
    }
    else {
        println("either '$arg1' or '$arg2' is not a number")
    }
}

2、只适用于当前变量不可变的情况(即在检查和使用之间没有修改过的局部变量 ,或者不可覆盖并且有幕后字段的 val 成员),因为否则可能会发生在检查之后 b 又变为null 的情况。否则需要再次在条件中检查null

(二)安全的调用:?.

1、使用安全调用操作符?. 表示如果非空那么调用属性或者方法,如果为空,则返回null
如果变量a非空,就返回a.length,否则返回null,a?.length的返回值类型为Int?

val a: String? = null
println(a?.length) // 打印null

2、链式调用中的使用:比如通过员工 bob, 获取他所在部门(department)的负责人(head)的名字(name),如果任意一个属性为空,那么这个链式调用返回空

var name = bob?.department?.head?.name

3、安全调用符?. 可以与let一起使用,会忽略掉item为null的处理

var listHasNull = listOf("danke", null, "outa")
for (item in listHasNull) {
    item?.let {
        println(it) // 忽略null
    }
}

打印结果:
在这里插入图片描述
4、安全调用符?.可以出现在赋值的左侧,如果调用链中的任何一个属性为空,都会跳过这个赋值操作,表示右侧的表达式根本不会求值

bob?.department?.head?.name = getName()

(三)Elvis 操作符:?:

1、获取字符串str的长度,如果str为空则返回-1

(1)使用if-else表达式

var str: String? = null
val len: Int = if (str != null) str.length else -1

使用if-else表达式,会有提示建议使用Elvis 操作符
在这里插入图片描述
(2)使用Elvis 操作符:?:,如果?:的左侧表达式非空,那么就会返回?:左侧表达式,否则返回右侧表达式(当且仅当左侧为空时,才会执行右侧的表达式)

var str: String? = null
val len: Int = str?.length ?: -1
2、Elvis 操作符结合throw和return使用

在Kotlin中,throwreturn都是表达式,所以它们也可以使用在?:的右侧
【例子】获取当前菜Instance的父菜的id,并且检查当前菜和父菜是否为空,父菜的唯一标识id,是否为空

class Instance {
    fun getParent(): Instance? {
        return Instance()
    }

    fun getId():String? {
        return null
    }
}

fun foo(instance: Instance?): String? {
    val parent = instance?.getParent() ?: return null // 如果instance为空,或者instance.getParent()为空,都会返回null
    val id = parent.getId() ?: throw IllegalArgumentException("id expected")
    return "parentId: $id"
}

(四)操作符:!!

非空断言运算符!!将任何值转换为非空类型,若该值为空则抛出异常KotlinNullPointerException

var str: String? = null
println(str!!.length) // 如果str == null时,运行报错kotlin.KotlinNullPointerException

(五)安全的类型转换:as?

1、类型转换时,如果对象不是目标类型,则会报ClassCastException
2、使用安全的类型转换as?,如果尝试转换不成功返回null

var a = "123"
var aInt: Int? = a as? Int
println(aInt)

(六)可空类型的集合:filterNotNull

使用filterNotNull可以过滤掉可空类元素集合中的空元素

var listHasNull = listOf("danke", null, "outa")
val listNotNull: List<String> = listHasNull.filterNotNull()
listNotNull.forEach {
    println(it)
}

打印结果:
在这里插入图片描述

四、Kotlin可能中会出现的NPE

1、显示调用 throw NullPointerException()
2、使用了非空断言运算符!!
3、数据在初始化时不一致:
(1)传递一个在构造函数中出现的未初始化的this,并用于其他地方(“泄漏this”)
(2)父类的构造函数调用一个开放成员,该成员在子类的实现使用了未初始化的状态
4、Java互操作
(1)企图访问平台类型的 null 引用的成员;
(2)用于具有错误可空性的 Java 互操作的泛型类型,例如一段 Java 代码可能会向Kotlin 的 MutableList<String> 中加入 null ,这意味着应该使用MutableList<String?> 来处理它;
(3)由外部 Java 代码引发的其他问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值