
一. ?、!! 、lateinit 以及 let
Kotlin 的类型系统旨在从我们的代码中消除 NullPointerException。
1.1 ?
Kotlin基于Java的空指针提出了一个空安全的概念,即每个属性默认不可为null。
例如:
var a: String = "test kotlin"a = null //编译错误
如果要允许为空,我们需要手动声明一个变量为可空字符串类型,写为String?
var a: String? = "test kotlin"a = null //编译成功
1.2 !!
!!是非空断言运算符。将任何值转换为非空类型,若该值为空则抛出异常。
object Test {var s:String?=null@JvmStaticfun main(args: Array<String>) {println(s!!.length)}}
执行上述代码会抛出如下异常。
Exception in thread "main" kotlin.KotlinNullPointerException
在App快要发布时,我们会进行检查尽量避免使用“!!”,转而考虑使用lateinit或者let函数来代替它。
1.3 lateinit
在某个类中,如果某些成员变量没办法在一开始就初始化,并且又不想使用可空类型(也就是带?的类型)。那么,可以使用lateinit来修饰它。
被lateinit修饰的变量,并不是不初始化,它需要在生命周期流程中进行获取或者初始化。
如果访问未初始化的 lateinit 变量会导致 UninitializedPropertyAccessException。
1.4 let函数
let函数把当前对象作为闭包的it参数,返回值是函数里面最后一行,或者指定return。它看起来有点类似于run函数。
let函数跟run函数的区别是:let函数在函数内可以通过 it 指代该对象。
/*** Calls the specified function [block] with `this` value as its argument and returns its result.*/@kotlin.internal.InlineOnlypublic inline fun <T, R> T.let(block: (T) -> R): R {contract {callsInPlace(block, InvocationKind.EXACTLY_ONCE)}return block(this)}
跟?结合使用, let函数可以在对象不为 null 的时候执行函数内的代码,从而避免了空指针异常的出现。
一般是这样使用:
?.let {....}
在使用Kotlin高效地开发Android App(二)中,曾经介绍过结合run和apply函数一起使用的方式。其实,里面使用了“!!”是有隐患的。
viewModel.email.run {if (value!!.isEmpty()) {toast(resources.getString(R.string.you_have_not_completed_the_email_address)).show()return@onClickRight}if (!Util.checkEmail(value!!)) {toast(resources.getString(R.string.the_email_format_you_have_filled_is_incorrect)).show()return@onClickRight}viewModel}.subject.run {if (value!!.isEmpty()) {toast(resources.getString(R.string.you_have_not_completed_the_feedback_subject)).show()return@onClickRight}viewModel}.content.apply {if (value!!.isEmpty()) {toast(resources.getString(R.string.you_have_not_completed_the_details)).show()return@onClickRight}}
可以使用let函数进行优化,避免出现空指针的情况。
viewModel.email.run {value?.let {if (it.isEmpty()) {toast(string(R.string.you_have_not_completed_the_email_address)).show()return@onClickRight}if (!Util.checkEmail(it)) {toast(string(R.string.the_email_format_you_have_filled_is_incorrect)).show()return@onClickRight}}viewModel}.subject.run {value?.let {if (it.isEmpty()) {toast(string(R.string.you_have_not_completed_the_feedback_subject)).show()return@onClickRight}}viewModel}.content.apply {value?.let {if (it.isEmpty()) {toast(string(R.string.you_have_not_completed_the_details)).show()return@onClickRight}}}
二.函数的默认参数
在Kotlin中,函数可以拥有默认参数,这样一来就不再需要像Java那样为了默认参数而写一大长串重载函数了。
例如,我们使用RxBinding时,可能会考虑到防止UI控件被重复点击,于是写下了这样的Transformer
/*** 防止重复点击的Transformer*/@JvmStaticfun <T> preventDuplicateClicksTransformer(windowDuration:Long=1000,timeUnit: TimeUnit=TimeUnit.MILLISECONDS): ObservableTransformer<T, T> {return ObservableTransformer { upstream ->upstream.throttleFirst(windowDuration, timeUnit)}}
在1秒内不能重复点击某个UI控件,可以这样写,因为使用了默认参数。
RxView.clicks(textview).compose(RxJavaUtils.preventDuplicateClicksTransformer()).subscribe({......})
三.DSL的使用
去年的时候,我曾经写过一篇关于kotlin dsl的文章——用kotlin来实现dsl风格的编程,使用dsl的方式编写代码个人感觉更加简洁和直观。
在项目中,我对toast以及glide框架尝试使用dsl的方式来封装。之前的用法是使用Kotlin的扩展函数,由于团队的其他成员更偏好链式调用,目前暂时保留了两种写法。
3.1 对glide的封装
glide的扩展函数,可以满足项目中的使用。
/*** 占位符矩形*/fun ImageView.load(url: String?) {get(url).placeholder(R.drawable.shape_default_rec_bg).error(R.drawable.shape_default_rec_bg).into(this)}/*** 占位符圆角矩形*/fun ImageView.loadRound(url: String?, centerCrop: Boolean = false) {get(url).placeholder(R.drawable.shape_default_round_bg).error(R.drawable.shape_default_round_bg).transform(RoundedCornersTransformation(DisplayUtil.dp2px(context, 10f), 0, centerCrop = centerCrop)).into(this)}/*** 占位符圆形*/fun ImageView.loadCircle(url: Drawable?) {get(url).placeholder(R.drawable.shape_default_circle_bg).apply(RequestOptions.circleCropTransform()).error(R.drawable.shape_default_circle_bg).into(this)}fun ImageView.loadCircle(url: String?) {get(url).placeholder(R.drawable.shape_default_circle_bg).apply(RequestOptions.circleCropTransform()).error(R.drawable.shape_default_circle_bg).into(this)}fun ImageView.get(url: String?): GlideRequest<Drawable> = GlideApp.with(context).load(url)fun ImageView.get(url: Drawable?): GlideRequest<Drawable> = GlideApp.with(context).load(url)
加载某个图片之后,让它呈现出圆角矩形的效果
holder.itemView.iv_game.loadRound(image_url)
使用dsl进行封装
class GlideWrapper {var url:String? = nullvar image: ImageView?=nullvar placeholder: Int = R.drawable.shape_default_rec_bgvar error: Int = R.drawable.shape_default_rec_bgvar transform: Transformation<Bitmap>? = null}fun load(init: GlideWrapper.() -> Unit) {val wrap = GlideWrapper()wrap.init()execute(wrap)}private fun execute(wrap:GlideWrapper) {wrap.image?.let {var request = it.get(wrap.url).placeholder(wrap.placeholder).error(wrap.error)if (wrap?.transform!=null) {request.transform(wrap.transform!!)}request.into(it)}}
仍然是加载该图片,让它呈现出圆角矩形的效果
load {url = image_urlimage = holder.itemView.iv_gametransform = RoundedCornersTransformation(DisplayUtil.dp2px(context, 10f), 0, centerCrop = false)}
3.2 对toast的封装
提示信息是任何App必不可少的,在我们的项目中也使用扩展函数对toast进行封装。
fun Toast.setGravityCenter(): Toast {setGravity(Gravity.CENTER, 0, 0)return this}/*** 设置Toast字体及背景颜色* @param messageColor* @param backgroundColor* @return*/fun Toast.setToastColor(@ColorInt messageColor: Int, @ColorInt backgroundColor: Int) {val view = viewif (view != null) {val message = view.findViewById(android.R.id.message) as TextViewmessage.setBackgroundColor(backgroundColor)message.setTextColor(messageColor)}}/*** 设置Toast字体及背景* @param messageColor* @param background* @return*/fun Toast.setBackground(@ColorInt messageColor: Int = Color.WHITE, @DrawableRes background: Int = R.drawable.shape_toast_bg): Toast {val view = viewif (view != null) {val message = view.findViewById(android.R.id.message) as TextViewview.setBackgroundResource(background)message.setTextColor(messageColor)}return this}//@SuppressLint("ShowToast")fun toast(text: CharSequence): Toast = Toast.makeText(App.instance, text, Toast.LENGTH_LONG).setGravityCenter().setBackground()//需要的地方调用withErrorIcon,默认不要添加// .withErrorIcon()//@SuppressLint("ShowToast")fun toast(@StringRes res: Int): Toast = Toast.makeText(App.instance, App.instance.resources.getString(res), Toast.LENGTH_LONG).setGravityCenter().setBackground()//需要的地方调用withErrorIcon,默认不要添加// .withErrorIcon()fun Toast.withErrorIcon(@DrawableRes iconRes: Int = R.drawable.ic_toast_error): Toast {val view = viewif (view != null) {val layout = this.view as LinearLayoutval layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)val icon = ImageView(getApplicationContext())icon.setImageResource(iconRes)icon.setPadding(0, 0, Util.dip2px(8f), 0)icon.layoutParams = layoutParamslayout.orientation = LinearLayout.HORIZONTALlayout.gravity = Gravity.CENTER_VERTICALlayout.addView(icon, 0)}return this}fun Toast.withSuccIcon(@DrawableRes iconRes: Int = R.drawable.ic_right_circle): Toast {val view = viewif (view != null) {val layout = this.view as LinearLayoutval layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)val icon = ImageView(getApplicationContext())icon.setImageResource(iconRes)icon.setPadding(0, 0, Util.dip2px(8f), 0)icon.layoutParams = layoutParamslayout.orientation = LinearLayout.HORIZONTALlayout.gravity = Gravity.CENTER_VERTICALlayout.addView(icon, 0)}return this}
要展示一个错误的提示,大致需要这样写。
toast(resources.getString(R.string.you_have_not_completed_the_email_address)).withErrorIcon().show()
使用dsl进行封装
class ToastWrapper {var text:String? = nullvar res:Int? = nullvar showSuccess:Boolean = falsevar showError:Boolean = false}fun toast(init: ToastWrapper.() -> Unit) {val wrap = ToastWrapper()wrap.init()execute(wrap)}private fun execute(wrap:ToastWrapper) {var taost:Toast?=nullwrap.text?.let {taost = toast(it)}wrap.res?.let {taost = toast(it)}if (wrap.showSuccess) {taost?.withSuccIcon()} else if (wrap.showError) {taost?.withErrorIcon()}taost?.show()}
使用dsl的方式展示同样的错误信息。
toast {res = R.string.you_have_not_completed_the_email_addressshowError = true}
总结
目前该系列的文章整理得比较随意,更像是一些常用的tips。
文中的dsl还是结合了扩展函数来使用的,个人认为是进一步的封装。相比起链式调用,我还是比较偏向dsl。
该系列的相关文章:
关注【Java与Android技术栈】
更多精彩内容请关注扫码:

本文探讨Kotlin中处理空值的方法,包括使用?、!!、lateinit及let函数等特性来避免NullPointerException。同时介绍了函数默认参数的使用,并通过实例展示了如何利用DSL风格进行代码封装,提高开发效率。
1317

被折叠的 条评论
为什么被折叠?



