ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] ### **“let”函数** let 函数让处理可空表达式变得更容易。和安全调用运算符一起使用,可以对表达式求值,检查求值结果是否为null,并把结果保存为一个变量。所有这些动作都在同一个简洁的表达式中。 可空参数最常见的一种用法应该就是被传递给一个接收非空参数的函数。比如说下面这个sendEmailTo 函数,它接收一个String 类型的参数井向这个地址发送一封邮件。 ~~~ fun main(args: Array<String>) { val email:String?="123456@163.com" sendEmailTo(email)//这里报错如下,就是说,这个函数需要一个非空参数,而此时传入了可空参数 // Error:(5, 17) Kotlin: Type mismatch: inferred type is String? but String was expected } fun sendEmailTo(email:String){/*.....*/} ~~~ 必须要显示地检查这个值是否为空,比如修改为`if (email != null) sendEmailTo(email) ` 这里还可以这样处理(使用let函数),使用let 函数,并通过安全调用来调用它。let函数做的所有事情就是把一个调用它的对象变成lambda 表达式的参数。如果结合安全调用语法,它能有效地把调用let 函数的可空对象,转变成非空类型 ![](https://box.kancloud.cn/c9d8c894e08689480c369bd615ae7cf0_562x157.png) 安全调用“ let”只在表达式不为null 时执行lambda 上面的代码就可这样写 ~~~ email?.let { email -> sendEmailTo(email) }//let 函数只在email 的值非空时才被调用 ~~~ 使用自动生成的名字it 这种简明语法之后,上面的代码就更短了: ~~~ email?.let { sendEmailTo(it) } ~~~ 完整代码如下 ~~~ fun main(args: Array<String>) { var email:String?="wsc123@163.com" // //直接下面这种方式,会报错 // sendEmailTo(email)//这里报错如下,就是说,这个函数需要一个非空参数,而此时传入了可空参数 // //第一种方法 // if (email!=null){ // // sendEmailTo(email) // } //第二种方法使用let函数 email?.let { email -> sendEmailTo(email) } //使用自动生成的名字it 这种简明语法,代码更简短 email?.let { sendEmailTo(it) } email = null email?.let { sendEmailTo(it) } } fun sendEmailTo(email:String){ println("Send email to $email") } ~~~ 运行结果 ``` Send email to wsc123@163.com Send email to wsc123@163.com Process finished with exit code 0 ``` 注意,如果有一些很长的表达式结果不为null,而你又要使用这些结果时,let表示法特别方便。这种情况下你不必创建一个单独的变量。对比一下显式的if检查 **if显示检查** ~~~ val person:Person?=getTheBestPersonTheWorld() if(person !=null) sendEmailTo(person.email) ~~~ **let函数代码** ~~~ getTheBestPersonTheWorld()?.let{sendEmailTo(it.email)} ~~~ 当你需要检查多个值是否为null 时,可以用嵌套的let 调用来处理。但在大多数情况下,这种代码相当啰嗦又难以理解。用普通的if 表达式来一次性检查所有值通常更简单。 ### **可空类性的扩展** 为可空类型定义扩展函数是一种更强大的处理null 值的方式。可以允许接收者为null 的(扩展函数)调用,并在该函数中处理null ,而不是在确保变量不为null之后再调用它的方法。只有扩展函数才能做到这一点,因为普通成员方法的调用是通过对象实例来分发的,因此实例为null 时(成员方法)永远不能被执行。 Kotlin 标准库中定义的String 的两个扩展函数isEmpty和isBlank就是这样的例子。第一个函数判断字符串是否是一个空的字符串"" 。第二个函数则判断它是否是空的或者它只包含空白字符。 示例如下 ~~~ fun main(args: Array<String>) { verifyUserInput(" ") verifyUserInput(null)//这个接受者调用isNullOrBlank并不会导致任何异常 } fun verifyUserInput(input:String?){ if (input.isNullOrBlank()){ println("Please fill in the required fields") } } ~~~ 运行结果 ``` Please fill in the required fields Please fill in the required fields Process finished with exit code 0 ``` 不需要安全访问,可以直接调用为可空接收者声明的扩展函数(如下图所示)。这个函数会处理可能的null值。 ![](https://box.kancloud.cn/95638baa8de54663180a11d59af12ceb_401x167.png) 函数isNullOrBlank 显式地检查了null ,这种情况下返回true ,然后调用isBlank ,它只能在非空String 上调用: ~~~ //可空字符串的扩展,第二个this使用了智能转换 fun String?.isNullOrBlank():Boolean = this ==null || this.isBlank() ~~~ 当你为一个可空类型(以?结尾)定义扩展函数时,这意味着你可以对可空的值调用这个函数;并且**函数体中的this 可能为null **,所以你**必须显式地检查**。在Java 中, this 永远是非空的,因为它引用的是当前你所在这个类的实例。而在Kotlin 中,这并不永远成立:**在可空类型的扩展函数中, this 可以为null** 。 >[info]注意:当你定义自己的扩展函数时,需要考虑该扩展是否需要为可空类型定义。默认情况下,应该把它定义成非空类型的扩展函数。如果发现大部分情况下需要在可空类型上使用这个函数,你可以稍后再安全地修改它(不会破坏其他代码) 。 ### **类型参数的可空性** Kotlin 中所有泛型类和泛型函数的类型参数默认都是可空的。任何类型,包括可空类型在内,都可以替换类型参数。这种情况下,使用类型参数作为类型的声明都允许为null,尽管类型参数T井没有用问号结尾。 ~~~ fun main(args: Array<String>) { printHashCode(null)//“T”被推导成“Any?” } fun <T> printHashCode(t:T){ println(t?.hashCode())//因为t可能是null,所以必须使用安全调用 } ~~~ 在printHashCode调用中,类型参数T 推导出的类型是可空类型Any?。因此,尽管没有用问号结尾,实参t 依然允许持有null 。 要使类型参数非空,必须要为它指定一个非空的上界,那样泛型会拒绝可空值作为实参。 ~~~ fun main(args: Array<String>) { printHashCode(42) //printHashCode(null)//这段无法编译,因为不能传递null,需要的是非空值 //Error:(5, 5) Kotlin: Type parameter bound for T in fun <T : Any> printHashCode(t: T): Unit is not satisfied: inferred type Nothing? is not a subtype of Any } fun <T:Any> printHashCode(t:T){//现在的“T”就不是可空的 println(t?.hashCode()) } ~~~ 运行结果 ``` 42 Process finished with exit code 0 ```