Kotlin知识点

574 阅读5分钟

让你的 Kotlin 代码远离 !!

简评:优雅的运用 Kotlin 的 null safety 特性,而不要简单的直接用 !!。

对于 Null 的检查是 Kotlin 的特点之一。强制你在编码过程中考虑变量是否可为 null,因此可以避免很多在 Java 中隐藏的 NullPointerException。

但是,当你用插件直接将 Java 代码转换为 Kotlin 时,你会发现有很多 !! 在里面。但其实 !! 意味着「有一个潜在未处理的 KotlinNullPointerException 在这里」。

这里就介绍 6 个避免 !! 的方法:

1. 用 val 而不是 var

在 Kotlin 中 val 代表只读,var 代表可变。建议尽可能多的使用 val。val 是线程安全的,并且不需要担心 null 的问题。只需要注意 val 在某些情况下也是可变的就行了。

可以看看这里: Mutable vals in Kotlin

 

2. 使用 lateinit

有些情况我们不能使用 val,比如,在 Android 中某些属性需要在 onCreate() 方法中初始化。对于这种情况,Kotlin 提供了 lateinit 关键字。

private lateinit var mAdapter: RecyclerAdapter<Transaction>
 
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   mAdapter = RecyclerAdapter(R.layout.item_transaction)
}
 
fun updateTransactions() {
   mAdapter.notifyDataSetChanged()
}

要注意,访问未初始化的 lateinit 属性会导致 UninitializedPropertyAccessException。

并且 lateinit 不支持基础数据类型,比如 Int。对于基础数据类型,我们可以这样:

private var mNumber: Int by Delegates.notNull<Int>()

3. 使用 let 函数

下面是 Kotlin 代码常见的编译错误:

许多开发者都会选择 quick-fix:

private var mPhotoUrl: String? = null
 
fun uploadClicked() {
    if (mPhotoUrl != null) {
        uploadPhoto(mPhotoUrl!!)
    }
}

但这里选择 let 函数是一个更优雅的解决方法:

private var mPhotoUrl: String? = null
 
fun uploadClicked() {
    mPhotoUrl?.let { uploadPhoto(it) }
}

4. 创建全局函数来处理更复杂的情况

let 是一个对于 null 检查很好的替代品,但有时我们会遇到更复杂的情况。比如:

if (mUserName != null && mPhotoUrl != null) {
   uploadPhoto(mUserName!!, mPhotoUrl!!)
}

你可以选择嵌套两个 let,但这样可读性并不好。这时你可以构建一个全局函数:

fun <T1, T2> ifNotNull(value1: T1?, value2: T2?, bothNotNull: (T1, T2) -> (Unit)) {
   if (value1 != null && value2 != null) {
       bothNotNull(value1, value2)
   }
}

用法如下:

var name: String? = null
var age: Int? = 1

ifNotNull(name, age, { _, _ -> println("都不为空")})

ifNotNull(age, name, { age, name -> getStudentAge(age, name) })
ifNotNull(age, name, { age, name -> getStudentName(age, name) })


/**
 * 泛型有返回值
 */
fun getStudentName(age : Int?, name : String?) : String {
    return "jack"
}
 
/**
 * 泛型无返回值
 */
fun getStudentAge(age : Int?, name : String?) {
 
}



​​​​​5. 使用 Elvis 运算符

Elvis 运算符在 Groovy 和 PHP 等语言中都存在。对于当值可能为 null 的情况特别方便:

fun getUserName(): String {
   if (mUserName != null) {
       return mUserName!!
   } else {
       return "Anonymous"
   }
}

上面的代码就可以简化为:

fun getUserName(): String {
   return mUserName ?: "Anonymous"
}

为什么叫 Elvis 呢?因为 ?: 很像猫王的发型:

6. 自定义崩溃信息

如果我们使用 !!,那么当这个变量为 null 时,只会简单的抛出一个 KotlinNullPointerException。这时我们可以用 requireNotNull 或 checkNotNull 来附带异常信息,方便我们调试。

uploadPhoto(requireNotNull(intent.getStringExtra("PHOTO_URL"), { "Activity parameter 'PHOTO_URL' is missing" }))

总而言之,绝大多数情况下你都不需要 !!,可以用上面提到的 6 个技巧来消除 !!。这样能让代码更安全、更容易 debug 并且更干净。

原文: How to remove all !! from your Kotlin code

 

Kotlin中 !!. 跟 ?. 的区别跟用法

在Kotlin中!!跟?都是用于判断空参数异常的

?.意思是这个参数可以为空,并且程序继续运行下去

!!.的意思是这个参数如果为空,就抛出异常

下面给大家举个简单的例子,在JAVA中判断一个参数非空是这样写的:

NullClass nullClass = null;
        
if (nullClass!=null) {
    ullClass.nullFun();
 }

将这段代码转换成Kotlin之后呢

val nullClass: NullClass? = null
 
nullClass?.nullFun()

短短两行就表述完了

在一开始的时候我们声明了一个类,并且在类名后面加了一个? 意思就是这个类可以为空,然后在下面用到这个类里面的一个方法时又加了一个问号,意思就是,当程序运行到这一行时,如果这个参数为空,就跳过这一行,程序继续执行下去

所以?.的用法就是相当于Java里的if()判断null

if (nullClass!=null) {//如果判断为空
//跳过这一行,程序继续执行
nullClass.nullFun();
}
一般?.的用法是:

在新建一个参数的类名后面加一个? 表示这个参数可以为空.

还有就是在用到这个参数的时候后面加? 表示空参数就跳过并且程序继续执行

而!!只用于用到这个参数的时候在后面加!!,表示空参数就抛出异常

还是相同的例子:

val nullClass: NullClass?=null
 
nullClass!!.nullFun()

换成Java代码就是

NullClass nullClass = null;
        
if (nullClass!=null) {
    nullClass.nullFun();
}else {
    throw new NullPointerException();
}


这里大家应该都能看明白了,在第二行参数后面加个!!,意思就是当程序执行到这行,判断这个参数如果是空参数,就抛出异常

所以!!.的用法就是相当于Java里的if()else()判断null

if (nullClass!=null) {//如果判断为空
    nullClass.nullFun();
}else {//抛出空参数异常
    throw new NullPointerException();
}

Kotlin采用let处理为空及非空的场景(类似if else)

Kotlin中使用?.let来处理对象非空时的逻辑,那如果为空又怎么办呢?

解决方法是结合?:

str?.let {
    //非空怎么撸
} ?: let {
    //为空又怎么撸
}

 isNotEmpty 和isNotBlank的区别

isNotEmpty(str)等价于 str != null && str.length > 0
isNotBlank(str) 等价于 str != null && str.length > 0 && str.trim().length > 0
同理
isEmpty 等价于 str == null || str.length == 0
isBlank  等价于 str == null || str.length == 0 || str.trim().length == 0