对于 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)
}
}

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 的 null safety 特性,而不要简单的直接用 !!双感叹号的更多相关文章

  1. dart系列之:安全看我,dart中的安全特性null safety

    目录 简介 Non-nullable类型 Nullable List Of Strings 和 List Of Nullable Strings !操作符 late关键字 总结 简介 在Dart 2. ...

  2. 再见NullPointerException。在Kotlin里null的处理(KAD 19)

    作者:Antonio Leiva 时间:Apr 4, 2017 原文链接:https://antonioleiva.com/nullity-kotlin/ 关于Kotlin最重要的部分之一:无效处理, ...

  3. Memcached概念、作用、运行原理、特性、不足简单梳理(1)

    大家可能对memcached这种产品早有了解,或者已经应用在自己的网站中了,但是也有一些朋友从来都没有听说过或者使用过.这都没什么关系,本文旨在从各个角度综合的介绍这种产品,尽量深入浅出,如果能对您现 ...

  4. Spring Boot 2.3.0正式发布:优雅停机、配置文件位置通配符新特性一览

    当大潮退去,才知道谁在裸泳..关注公众号[BAT的乌托邦]开启专栏式学习,拒绝浅尝辄止.本文 https://www.yourbatman.cn 已收录,里面一并有Spring技术栈.MyBatis. ...

  5. MYSQL NULL值特性

    NULL是一种“没有类型”的值,通常表示“无值”,“未知值”,“缺失值”,“超界”,“不在其中”等,我们在日常运用中很容易和NULL字符串混淆,这里大致整理了下NULL值的一些特性,以便能够正确使用N ...

  6. 如何优雅的使用 参数 is null而不导致全表扫描(破坏索引)

    相信大家在很多实际业务中(特别是后台系统)会使用到各种筛选条件来筛选结果集 首先添加测试数据 ), Age INT) go CREATE INDEX idx_age ON TempList (Age) ...

  7. kotlin之null值安全性

    var a: String =null // 编译错误 var a: String? =null // 编译通过 要允许null值, 需要将变量声明为可为null的字符串类型:String? fun ...

  8. C# 使用Conditional特性而不是#if条件编译

    概述 #if/#endif 语句常用来基于同一份源码生成不同的编译结果,其中最常见的就是debug版和release版.但是这些工具在实际应用中并不是非常友好,因为它们容易被滥用,其代码页进而难以理解 ...

  9. C#中检测某个类(方法、程序集等各种部分)是否应用了指定的特性以及对特性的一些简单操作

    前言:不管是自定义的一些特性,或者是C#中内置的特性,均继承自Attribute这个类,这个类也提供了一些方法,方便我们使用. Attribute类有三个静态方法:1.IsDefined,如果有指定的 ...

随机推荐

  1. js两个日期相减

    function dateHanle(d1,d2){ if(Date.parse(d1) - Date.parse(d2)==0) { console.log("d1等于d2"); ...

  2. activiti工作流之Eclipse的Eclipse BPMN 2.0 Designer无法安装或者(安装后无法重复打开*.bpmn)

    1.首先.既然学习activiti工作流,连官网和相应文件都没有下载就说不过去了 这是官网下载:http://www.activiti.org/download.html 2.对于下载后的activi ...

  3. iframe调用父页面函数用法举例

    iframe如何调用父页面函数. window.parent.xxxxx();//xxxxx()代表父页面方法具体列子如下,其中包括easyUI的右键和单击事件parent.jspbody部分代码 & ...

  4. iOS缓存-内存缓存

    为了减少与服务器的连接沟通次数,提高应用程序的执行速度,使用了iOS的缓存机制 #import "YoungViewController.h" @interface YoungVi ...

  5. Redis为什么使用单进程单线程方式

    Redis采用的是基于内存的采用的是单进程单线程模型的KV数据库,由C语言编写.官方提供的数据是可以达到100000+的qps.这个数据不比采用单进程多线程的同样基于内存的KV数据库Memcached ...

  6. 加快android studio 编译速度(已更新至Android Studio 3.3.1)

    1.加快AS启动速度 “Help”-"Edit Custom Properties...",在文件中输入 # custom Android Studio properties di ...

  7. Adding support for distinct operation for table API on DataStream

    https://github.com/apache/flink/pull/6521/files/66c3bd5d52a5e4af1f83406035b95774e8b6f636#diff-680b30 ...

  8. DIOCP-开源项目ECHO测试.

    DIOCP自开源以来,得到了很多朋友的测试,并进行了诸多的改进,现在已经运用到了一些具体的项目当中. DIOCP底层运行稳定. 昨天做了个ECHO测试,这个连接数并没有达到上限. 11K 连接,1个半 ...

  9. java中的动态加载和热替换

    https://blog.csdn.net/u010833547/article/details/54312052 ****************************************** ...

  10. Oracle 大小写转换函数

    Oracle 大小写转换函数 转大写UPPER 转小写LOWER 测试: select UPPER('Test') as u from dual; select LOWER('Test') as l ...