翻译说明:android
原标题: How to remove all !! from your Kotlin code编程
原文地址: android.jlelse.eu/how-to-remo…安全
原文做者: David Vávra网络
空安全特性是Kotlin语言最好语法特性之一。它让你在语言层面来考虑可空性,以至于你能够避免不少在Java中常见的隐藏空指针异常。然而当你经过工具自动将Java代码转化成Kotlin时,你会发现有不少的 !!(非空断言) 标记出现。按道理在你的代码中不该该有任何的 !!(非空断言) 出现,除非它是一个快速原型。而且我相信这是对的,由于 !!(非空断言) 的出现基本上的意味着 “你这里有可能存在未处理的KotlinNullPointerException”.ide
Kotlin有一些智能的机制去避免这些空指针的问题,可是弄明白它并非那么直接和容易。这里有6种方法去作到这一点:函数式编程
Kotlin让你在语言的层面去考虑不变性,这点看起来很不错。 val 是只读,var 是可变。建议你尽量多的使用只读属性。由于他们是线程安全的而且在函数式编程方面效果很好。若是你使用它们时当作是不可变的,那么你就没必要关心可空性了,可是只要注意val其实是可变的。函数
有时候你不能使用不变属性。例如,在Android中onCreate方法被调用时,一些属性被初始化。在这些场景中,Kotlin有个语言特性叫作 lateinit工具
使用!!的代码:post
private var mAdapter: RecyclerAdapter<Transaction>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mAdapter = RecyclerAdapter(R.layout.item_transaction)
}
fun updateTransactions() {
mAdapter!!.notifyDataSetChanged()
}
复制代码
用下面代码替代上面代码:ui
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. 针对基本数据类型实现方式你可使用委托(delegate)相似如下实现:
private var mNumber: Int by Delegates.notNull<Int>()
复制代码
这里有个Kotlin中很常见的编译时错误:
令我恼火的是:我知道这个可变属性在空类型检查后不能被改变。不少开发人员经过如下方式快速修复它:
private var mPhotoUrl: String? = null
fun uploadClicked() {
if (mPhotoUrl != null) {
uploadPhoto(mPhotoUrl!!)
}
}
复制代码
可是这里有个优雅解决办法,那就是使用 let函数
private var mPhotoUrl: String? = null
fun uploadClicked() {
mPhotoUrl?.let { uploadPhoto(it) }
}
复制代码
let 函数是一个很好的简单检查空类型替代方式,可是可能会出现更多复杂的cases,例如:
if (mUserName != null && mPhotoUrl != null) {
uploadPhoto(mUserName!!, mPhotoUrl!!)
}
复制代码
你可使用两个let函数嵌套调用,可是那样可读性不好。在Kotlin中你能够全局访问函数,所以你能够轻松地建立你所须要的函数。相似以下方法:
ifNotNull(mUserName, mPhotoUrl) {
userName, photoUrl ->
uploadPhoto(userName, photoUrl)
}
复制代码
这个函数定义代码:
fun <T1, T2> ifNotNull(value1: T1?, value2: T2?, bothNotNull: (T1, T2) -> (Unit)) {
if (value1 != null && value2 != null) {
bothNotNull(value1, value2)
}
}
复制代码
Elvis操做符做用不错在于若是你有空类型状况出现,会有返回值的功能。好比如下代码:
fun getUserName(): String {
if (mUserName != null) {
return mUserName!!
} else {
return "Anonymous"
}
}
复制代码
能够被替代以下代码:
fun getUserName(): String {
return mUserName ?: "Anonymous"
}
复制代码
尽管你知道类型是可空的,可是有些状况下你知道一些属性是不可能为空的。一旦为空了,你应该很容易知道这是一个bug.然而抛弃使用 !! 非空断言,系统就会给你抛出一个很难去debug的通用的常 KotlinNullPointerException。使用内置函数 requireNotNull 或者 checkNotNull 和一些附带的异常消息易于调试。相似以下代码:
uploadPhoto(intent.getStringExtra("PHOTO_URL")!!)
复制代码
以上代码能够替代为:
uploadPhoto(requireNotNull(intent.getStringExtra("PHOTO_URL"), { "Activity parameter 'PHOTO_URL' is missing" }))
复制代码
若是你按照这6个提示,你能够从你的Kotlin代码删除全部的 !! 非空断言。这样你的代码将更安全,更可调试,更清洁。
咱们知道Kotlin中一个很是好的特性就是空类型安全的特性,也就是极大程度上避免了像Java中的空指针问题。是否是表示我使用Kotlin就不会存在空指针了呢。能够这么说Kotlin空类型安全特性,对于会使用的人来讲将会是很是方便和安全,对于不会使用的人来讲(特别是一些初学者,包括还在用Java语言思想写Kotlin代码的人)空类型安全特性的代码会写得很是的ugly,例如译文中反面教材例子滥用 !!非空断言。可能不只仅是代码丑陋的问题,还很容易带来KotlinNullPointException. 若是你还在滥用!!(非空断言)处理Kotlin中空类型的话,看完本篇博客不妨尝试一种优雅的方式去实现空类型安全
第一对于尽可能多的使用val替代var这个建议,我在以前博客中屡次提到。它能够避免出现一些没必要要错误以及很好支持函数式编程。
第二就是关于使用lateinit的问题,须要特别补充一点,当你在使用lateinit的时候,必定要保证你使用的这个属性,必需要在它初始化以后使用。并且在开发中一个坑就是接收网络请求返回成功后的数据属性不要用lateinit的修饰,由于因为某种异常状况,你的网络请求失败,没法回调到成功callback中,此时你的属性没有被初始化,而代码执行到使用这个属性时候就会抛出上面所说的UninitializedPropertyAccessException异常。建议使用lateinit属性时,你很是清楚改属性初始化是在使用以前,好比通常在onCreate方法中初始化的一些属性就能够声明成lateinit.
关于空类型安全问题,其实还有不少须要注意的点,后续会有专门专题博客来阐述Kotlin中空类型安全的问题。而这篇译文则是先认识一下,以及在实际开发中如何优雅实现空类型安全的特性。
欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不按期翻译一篇Kotlin国外技术文章。若是你也喜欢Kotlin,欢迎加入咱们~~~