文章目录

  • ​​一、Kotlin 的空安全机制​​
  • ​​二、变量可空性​​
  • ​​1、默认变量不可赋空值​​
  • ​​2、声明可空类型变量​​
  • ​​三、手动空安全管理​​
  • ​​四、空安全调用操作符 ?​​
  • ​​五、let 函数结合空安全调用操作符使用​​
  • ​​六、非空断言操作符 !!​​
  • ​​七、使用 if 语句判空​​
  • ​​八、空合并操作符 ?:​​
  • ​​九、空合并操作符与 let 函数结合使用​​
  • ​​十、空指针异常处理​​
  • ​​1、捕获并处理异常​​
  • ​​2、抛出自定义异常​​
  • ​​十一、先决条件函数判空​​
  • ​​1、先决条件函数概念​​
  • ​​2、先决条件函数原型​​
  • ​​3、先决条件函数代码示例​​






一、Kotlin 的空安全机制



Java 中的空指针问题 :

在 Java 语言 编写的程序中 , 出现最多的崩溃就是 NullPointerException 空指针异常 ,

该异常是 运行时 才爆出的 , 在 代码编写时 以及 编译期



Kotlin 的空安全机制 :

在 Kotlin 语言 中 , 针对 空指针异常 问题 进行了优化 , 引入了 空安全机制 ,

在代码编写后的 编译期 , 就可以 提前排查出可能出现的空指针异常问题

这样极大地提高了 Kotlin 程序的 代码健壮性 ;






二、变量可空性



1、默认变量不可赋空值



在 Java 中 , 引用类型的变量 默认为 ​​null​​ 空值 ;

但是在 Kotlin 中 , 变量默认不可为 ​​null​​ 空值 ,

这样所有的 变量 在默认状态下 , 都有一个 默认的实例对象 ,

从而极大的 减少了 空指针异常 出现的概率 ;



代码示例 : 先定义一个 ​​name​​​ 变量 , 为其赋值字符串 ​​"Tom"​​ ,

然后再为其赋值 ​​null​​ 空值 ;

fun main() {
var name = "Tom"
name = null
}

此时 , 在 IntelliJ IDEA 中 就会提示如下报错信息 :

Null can not be a value of a non-null type String

【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )_空安全


这是因为 ​​ var name​​ 变量 默认为非空的 ,

在 Kotlin 中 不允许将 默认变量 赋值一个空值 ,

除非 将该变量声明为 可空类型 ;



2、声明可空类型变量



声明可空类型变量 :

如果要声明一个 可空类型的变量 , 必须 声明该变量的具体的类型 ,

并在该类型后添加 ​​?​​ 标志 , 具体格式如下 :

var 变量名: 变量类型?



代码示例 : 在下面的代码张红 ,

将 ​​var name​​​ 变量声明为了 ​​String?​​ 可空类型 ,

此时就可以为 该变量 赋值 ​​null​​ 值 ;

fun main() {
var name: String? = "Tom"
name = null
}

进行了 ​​String?​​ 可空类型声明后 , 在 IntelliJ IDEA 中 , 就不再进行报错了 ;

【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )_先决条件函数_02






三、手动空安全管理



Kotlin 语言中 , 变量类型 分为 可空类型 和 非空类型 ,

默认状态 下 , 变量是 非空类型 的 ,

如果使用 ​​类型?​​ 将变量声明为 可空类型 ,

那么就需要使用 手动安全管理 ;



代码示例 : 在下面的代码中 , 将 ​​name​​​ 变量声明为了 ​​String?​​ 可空类型 ,

那么 调用该可空类型变量 的 成员 或 方法 时 , 就不能直接调用了 ,

必须引入 手动安全管理 ;

fun main() {
var name: String? = "Tom"
name.count()
}

上述代码中 , 在调用该变量时 , 就会出现如下报错信息 :

Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )_空安全调用操作符_03






四、空安全调用操作符 ?



在 Kotlin 语言中 , 调用 可空类型变量 的 成员 时 ,

可以使用 " 安全调用操作符 " 也就是 ​​?​​ 进行调用 ,

使用格式如下 :

可空类型变量?.成员



使用了 安全调用操作符 之后 , 在调用变量成员前 , 会自动进行 空值检查 ,

如果该变量为空 , 则会 跳过后面的 成员调用 , 继续执行下一行代码 ;



代码示例 : 在下面的代码中 , 调用 ​​name​​​ 变量时 , 使用 ​​?​​​ 安全调用操作符 ​​name?.count()​​ 进行调用 ;

fun main() {
var name: String? = "Tom"
name?.count()
}

【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )_先决条件函数_04






五、let 函数结合空安全调用操作符使用



如果想要在 变量 原有基础上 , 继续执行其它操作 , 可以使用 let 标准函数 ;

安全调用操作符 经常与 ​​let​​ 标准函数 一起使用 ;



如 : 以 ​​name?.let{}​​​ 方式调用 ​​let​​ 函数 ,

其含义是 如果 ​​name​​​ 变量不为空 , 则调用 ​​let​​ 函数 ,

如果 ​​name​​​ 变量为空 , 则跳过后面的 ​​let​​ 函数执行 ;



代码示例 : 在下面的代码中 ,

将变量 ​​name​​​ 声明为了 可空类型 ​​String?​​ ,

为 ​​name​​​ 变量执行一些 附加操作 时 , 可使用 ​​?.let{}​​ 方式进行 ,

含义是 , 假如 ​​name​​​ 变量不为空 , 则执行 ​​let​​ 函数中的 Lambda 表达式内容 ,

let 函数 返回 匿名函数 最后一行 ,

Lambda 表达式 / 匿名函数 / 闭包 的含义是 ,

如果 ​​name​​ 变量 字符串非空白, 将其转为首字母大写 , 并返回 ,

如果 ​​name​​​ 变量 字符串为空白, 设置 ​​Hello​​ 值为返回值 ;

fun main() {
var name: String? = "tom"
name = name?.let {
// let 函数返回 匿名函数 最后一行
if(it.isNotBlank()) {
// 如果字符串非空白, 将其转为首字母大写
it.capitalize()
} else {
// 如果字符串为空白, 设置默认值
"Hello"
}
}
println(name)
}

执行结果 :

Tom

【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )_先决条件函数_05






六、非空断言操作符 !!



Kotlin 中的 可空类型 变量 , 在运行时 可以选择 不启用 安全调用 操作 ,

在调用 可空类型 变量 成员 与 方法 时 , 使用 非空断言操作符 ​​!!​​ ,

如果 可空类型 变量为 空 , 则 直接抛出 空指针异常 ​​KotlinNullPointerException​​ ;



代码示例 : 在下面的代码中 , ​​name​​​ 变量是 ​​String?​​ 可空类型 ,

变量值为 ​​null​​ ,

如果使用 ​​name?.count()​​​ 的方式调用 , 则会先判定 ​​name​​ 是否为空 , 如果为空则该代码不会执行 ,

如果使用 ​​name!!.count()​​​ 的方式调用 , 不会判定 ​​name​​ 是否为空 ,

如果 为空 抛出 ​​KotlinNullPointerException​​ 异常 ;

fun main() {
var name: String? = null
println(name!!.count())
}

执行结果 :

Exception in thread "main" kotlin.KotlinNullPointerException
at HelloKt.main(Hello.kt:3)
at HelloKt.main(Hello.kt)

【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )_可空性_06






七、使用 if 语句判空



在 Kotlin 中 , 对于 可空类型 变量的调用 , 除了使用

  • 空安全调用操作符 ​​?​
  • 非空断言操作符 ​​!!​

之外 , 还可以使用 Java 语言中的传统判空方式 ,

即 ​​if​​​ 语句判断 变量 是否为 ​​null​​ ;



空安全调用操作符 ? 与 使用 if 语句判空操作 对比 :

  • 空安全调用操作符 更加 灵活 , 简洁 ;
  • 空安全调用操作符 可以进行 链式调用 ;

二者的效果是等价的 ;



代码示例 1 : 下面的代码是 使用 if 语句判空

fun main() {
var name: String? = null
var count: Int? = null
if(name != null) {
count = name.count()
}
println(count)
}

执行结果 :

null

【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )_空安全调用操作符_07



代码示例 2 : 下面的代码 与 代码示例 1 的 效果是等价的 , 显然本代码更加简洁 ;

fun main() {
var name: String? = null
var count: Int? = name?.count()
println(count)
}

执行结果 :

null

【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )_可空性_08






八、空合并操作符 ?:



空合并操作符 ?: 用法 :

表达式 A ?: 表达式 B

如果 表达式 A 的值 不为 ​​null​​ , 则 整个表达式的值 就是 表达式 A 的值 ;

如果 表达式 A 的值 为 ​​null​​ , 则 整个表达式的值 就是 表达式 B 的值 ;



代码示例 : 在下面的代码中 ,

​name​​​ 变量 被声明为 ​​String?​​​ 可空类型的变量 , 为其赋值为 ​​null​​ ,

使用 ​​name ?: "name 变量为空"​​ 代码 , 其效果如下 :

空合并操作符 左侧的 ​​name​​​ 表达式如果为 ​​null​​ , 则 取 右边的 表达式 作为该表达式最终的值 ,

如果 左侧的 ​​name​​​ 表达式 不为 ​​null​​​ , 则 取 该 ​​name​​ 变量作为 该表达式最终的值 ;

因此 , 第一次使用 ​​name ?: "name 变量为空"​​​ 代码时 , ​​name​​​ 为空 , 整个表达式 ​​name ?: "name 变量为空"​​​ 返回的是 ​​"name 变量为空"​​​ 值 , 打印出来的就是 ​​name 变量为空​​ 内容 ;

之后 为 ​​name​​​ 变量赋值 ​​"Tom"​​​ 字符串值 , 现在 ​​name​​​ 变量不为空 , 使用 ​​name ?: "name 变量为空"​​​ 代码返回的是 ​​name​​​ 变量的值 , 因此打印出来的就是 ​​Tom​​ 内容 ;

fun main() {
var name: String? = null
println(name ?: "name 变量为空")

name = "Tom"
println(name ?: "name 变量为空")
}

执行结果 :

name 变量为空
Tom

【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )_可空性_09






九、空合并操作符与 let 函数结合使用



空合并操作符 ​​?:​​​ 与 let 函数 结合使用 , 可以 替代 ​​if .. else ..​​ 语句 ;



代码示例 :

fun main() {
var name: String? = null

name = "tom"
name = name?.let {
it.capitalize()
} ?: "Jerry"

println(name)
}

执行结果 :

Tom

【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )_先决条件函数_10



如果 ​​name​​​ 变量为 ​​null​​​ , 则 ​​?:​​​ 表达式中 , 会选择 ​​?:​​ 后面的表达式作为最终结果 ;

对应代码如下 :

fun main() {
var name: String? = null

name = name?.let {
it.capitalize()
} ?: "Jerry"

println(name)
}

执行结果 :

Jerry

【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )_空安全调用操作符_11






十、空指针异常处理



在 Kotlin 程序中 , 处理异常 的方式有 :

  • 抛出默认异常
  • 抛出自定义异常
  • 捕获并处理异常


1、捕获并处理异常



捕获异常代码示例 : 在下面的代码中 ,

​name​​​ 变量是可空类型变量 , 其初始值为 ​​null​​ ,

使用 非空断言操作符 ​​!!​​ 调用 变量 成员 , 不会进行 空值检查 ,

如果变量为空 , 则直接抛出 ​​kotlin.KotlinNullPointerException​​ 异常 ;

使用 ​​try .. catch ..​​ 代码块 , 可以捕获并处理异常 ;

import java.lang.Exception

fun main() {
var name: String? = null

// 捕获并处理异常
try {
name!!.count();
} catch (e: Exception) {
println(e)
}
}

执行结果 :

kotlin.KotlinNullPointerException

【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )_空安全调用操作符_12


2、抛出自定义异常



抛出自定义异常代码示例 : 在下面的代码中 ,

声明了自定义 异常类 ​​MyException​​​ , 其继承了 ​​KotlinNullPointerException​​ 空指针异常类 ,

在调用 ​​name​​​ 变量成员时 , 先调用 ​​checkNull​​ 函数 ,

检查该变量是否为空 ,

使用 ​​str ?: throw MyException()​​​ 代码 , 其中 空合并操作符 判定 ​​str​​ 是否为空 ,

如果为空 , 则抛出 ​​MyException​​ 异常 ;

import java.lang.Exception

fun main() {
var name: String? = null

// 捕获并处理异常
try {
checkNull(name)
name!!.count();
} catch (e: Exception) {
println(e)
}
}

fun checkNull(str: String?) {
str ?: throw MyException()
}

class MyException: KotlinNullPointerException("空指针")

执行结果 :

MyException: 空指针






十一、先决条件函数判空




1、先决条件函数概念



在 Kotlin 中提供了一些 内置函数 ,

在这些函数中可以抛出 携带自定义信息的异常 ,

这些函数 就是 " 先决条件函数 " ;



只有满足了 先决条件函数 的 先决条件 , 代码才能继续执行 , 否则就会抛异常 ;



2、先决条件函数原型



常用的先决条件函数如下 :

  • checkNotNull 函数 :
  • 参数为 ​​null​​​ , 抛出 ​​IllegalStateException​​ 异常 ,
  • 参数为非空 , 返回非空值 ;
/**
* 如果[value]为空,则抛出[IllegalStateException]。否则
* 返回非空值。
*
* @sample samples.misc.Preconditions.failCheckWithLazyMessage
*/
@kotlin.internal.InlineOnly
public inline fun <T : Any> checkNotNull(value: T?): T {
contract {
returns() implies (value != null)
}
return checkNotNull(value) { "Required value was null." }
}
  • require 函数 :
  • 参数为 ​​false​​​ , 抛出 ​​IllegalArgumentException​​ 异常 ;
/**
* 如果[value]为false,则抛出[IllegalArgumentException]。
*
* @sample samples.misc.Preconditions.failRequireWithLazyMessage
*/
@kotlin.internal.InlineOnly
public inline fun require(value: Boolean): Unit {
contract {
returns() implies value
}
require(value) { "Failed requirement." }
}
  • requireNotNull 函数 :
  • 参数为 ​​null​​​ , 抛出 ​​IllegalArgumentException​​ 异常 ;
  • 参数非空 , 返回非空值 ;
/**
* 如果[value]为空,则抛出[IllegalArgumentException]。否则返回非空值。
*/
@kotlin.internal.InlineOnly
public inline fun <T : Any> requireNotNull(value: T?): T {
contract {
returns() implies (value != null)
}
return requireNotNull(value) { "Required value was null." }
}
  • error 函数 :
  • 参数为 ​​null​​​ , 使用给定的 错误信息 抛出 ​​IllegalStateException​​ 异常 ;
  • 参数非空 , 返回非空值 ;
/**
* 使用给定的[message]抛出[IllegalStateException]。
*
* @sample samples.misc.Preconditions.failWithError
*/
@kotlin.internal.InlineOnly
public inline fun error(message: Any): Nothing = throw IllegalStateException(message.toString())
  • assert 函数 :
  • 参数为 ​​false​​​ , 抛出 ​​AssertionError​​ 异常 , 并进行 断言标记 ;
/**
* 如果[value]为false,则抛出[AssertionError]
* 和运行时断言已经使用*-ea* JVM选项在JVM上启用。
*/
@kotlin.internal.InlineOnly
public inline fun assert(value: Boolean) {
assert(value) { "Assertion failed" }
}



3、先决条件函数代码示例



代码示例 : 在执行 ​​name​​​ 字符串的 ​​count​​ 函数之前 ,

先使用 ​​checkNotNull(name, {"变量为空"})​​​ 先决条件函数 , 判定 ​​name​​ 是否为空 ,

如果为空 , 抛出带信息的 ​​IllegalStateException​​ 异常 信息 ;

fun main() {
var name: String? = null

// 捕获并处理异常
try {
checkNotNull(name, {"变量为空"})
name!!.count();
} catch (e: Exception) {
println(e)
}
}

执行结果 :

java.lang.IllegalStateException: 变量为空

【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )_可空性_13



上述使用的 先决条件函数 checkNotNull 原型 :

/**
* 如果[value]为空,则使用调用[lazyMessage]的结果抛出[IllegalStateException]。否则
* 返回非空值。
*
* @sample samples.misc.Preconditions.failCheckWithLazyMessage
*/
@kotlin.internal.InlineOnly
public inline fun <T : Any> checkNotNull(value: T?, lazyMessage: () -> Any): T {
contract {
returns() implies (value != null)
}

if (value == null) {
val message = lazyMessage()
throw IllegalStateException(message.toString())
} else {
return value
}
}