文章目录

  • 可空类型
  • 安全调用运算符 “?.”
  • 安全转换 “as?”
  • Elvis 运算符 “?:”
  • 非空断言 “!!”
  • “let” 函数
  • 延迟初始化的属性
  • 可空类型的扩展
  • 类型参数的可空性
  • 可空性和 Java


可空类型

java :
Type = Type or null kotlin :
Type = TypeType? = Type or null

如:
在 java 中,String 类型的变量,可能是 null,可能是 String 值。
在 kotlin 中,String 类型的变量只能是 String 值,String? 才代表 null 或 String 值。

当对一个可空类型进行操作时,如果不进行判空,是无法编译通过的。

var a: String? = "hello"
val len = a.length // 会报错
val len = if (a != null) a.length else 0 // 不会报错

以下内容都是对可空类型的处理方式。

安全调用运算符 “?.”

如果每次都进行判空显然太繁琐了,?. 运算符允许你把一次 null 检查和一次方法调用合并成一个操作。

s?.length
// 相当于
if (s != null) s.length else null

这也给对象的连续调用提供了方便:

val x = a?.b()?.c()?.d()
val x = a?.b?.c?.d()

安全转换 “as?”

as? 也很容易理解。

val c = a as? String
// 相当于
val c = if (a != null) a as String else null

Elvis 运算符 “?:”

可以看出,?.as? 都返回一个可控类型,即他们原来的期望结果或 null。

a?.b 返回 a.b or null,a as b 返回 a as b or null。

如果我们不希望看到返回 null,可以在他们后面使用 Elvis 运算符 ?: 来处理 null 的返回(叫 Elvis 运算符是因为他们觉得这个符号旋转 90° 后像猫王,哈哈)。

?: 很像 java 中三元表达式的简化版:

a ?: b
// 相当于 java 中的
a != null ? a : b
// 相当于 kotlin 中的
if (a != null) a else b

?.as? 配合使用:

var len: Int = s?.length ?: 0
var x: String = s as? String ?: ""

非空断言 “!!”

有时我们能够确保一个值不为空,可以使用 !! 来把可空类型转换为非空类型。如果为空时,会抛出异常。

fun a(b: String?) {
    println(b!!.length)
}
a("hello")
a(null)

// 输出
5
Exception in thread "main" kotlin.KotlinNullPointerException

“let” 函数

a?.b 可以在 a 非空时调用它的属性和方法。
但是如果我们想在 a 非空时对它进行另外的操作,比如把它传给另外的函数,可以使用 let 函数。

a?.let { b(it) }

let 函数把调用它的对象变成 lambda 表达式的参数。

延迟初始化的属性

如果我们需要定义一个非空属性,它在类中定义时是 null,随后在特定的方法中初始化为一个非空的值(如 android 的 onCreate 方法、JUnit 的 @before 方法)。

这在 java 中很简单(java 中类属性默认会是 null),但在 kotlin 中,要定义一个非空类型,必须在初始化的时候进行赋值,而如果定义为一个可空类型,在初始化方法中初始化后,它已经是非空的,但类型还是可空,需要进行额外的非空判断,这其实是不必要的。

这时可以使用 lateinit 关键字对属性进行修饰。

class MyTest {
    private lateinit var mySrvice: MyService
    // 不需要初始化
    
    @Before
    fun setUp() {
        myService = MySerive()
        // 延迟初始化
    }
    
    @Test
    fun testAction() {
        Assert.assertEquals("foo", myService.performAction())
        // 不需要 null 检查,直接访问
    }
}

可空类型的扩展

为可空类型定义扩展函数是一种更强大的处理 null 值的方式。如 String 类的 isNullOrEmpty 和 isNullOrBlank 方法。

// 在 java 中对 String 进行非空判断
if (s != null && !s.equals("")) {
    ...
}
// 在 kotlin 中
if (!s.isNullOrEmpty()){
    ...
}

isNullOrEmpty 方法:

public inline fun CharSequence?.isNullOrBlank(): Boolean {
    ...
    return this == null || this.isBlank()
}

可以看出,由于 kotlin 中有可空类型,所以可以对 CharSequence? 这个可空类型进行扩展,可以直接使用 this == null 这样的判断。

所以,当我们看到 s.isNullOrEmpty() 这样的调用时,它并不意味着 s 是非空的:这个函数有可能是非空类型的扩展函数。

类型参数的可空性

kotlin 中所有泛型类和泛型函数的参数类型都是可空的,即使它们没有用 ? 来结尾。

fun <T> printHashCode(t: T) {
    println(t?.hashCode()) // 因为 t 可能为空,所有必须使用安全调用
}

>>> printHashCode(null) // "T" 被推导为 "Any?"
null

如果要使参数非空,需要给它指定一个非空的上界。

fun <T: Any> printHashCode(t: T) {
    println(t.hashCode()) // 现在 t 就不是可空的了
}

>>> printHashCode(null) // 无法编译
>>> printHashCode(123)
123

可空性和 Java

kotlin 是可以和 java 交互的,但 java 里没有可空类型,这怎么办呢。

  1. java 中包含可空性的注解

kotlin 会对 java 进行转化

@Nullable + Type = Type?
@NotNull + Type = Type

(= 左边为java,右边为kotlin)

  1. java 中不包含可空性的注解

kotlin 不知道 java 对象的可空性时,会将它转化为“平台类型”,你既可以把它当做可空类型,也可以把它当做非空类型,就像在 java 代码里做的一样,你要自己负责 null 的判断。

Type = Type? or Type

(= 左边为java,右边为kotlin)