Kotlin标准库中的let
、with
、run
、apply
、also
这五个函数相当有意思。它们的实现非常简单,区区两三行,却直击了Java的若干痛点。
Kotlin对它们的定位是scope functions。这是什么意思呢?
我理解scope functions是指这些函数可以通过新的作用域操作对象,避免引入临时变量或一次性函数污染原作用域。
下面简要分析下这几个函数。
let
函数结构
object.let{ //在函数体内使用it替代object对象去访问其公有的属性和方法 it.todo() ... } //另一种用途 判断object为null的操作 //表示object不为null的条件下,才会去执行let函数体 object?.let{ it.todo() }
适用的场景
- 使用let函数处理需要针对一个可null的对象统一做判空处理(常用)
- 需要去明确一个变量所处特定的作用域范围内可以使用
例子
- 不使用let
mTextView?.textSize(...) mTextView?.setTextColor(...) mTextView?.background(..)
- 使用let
mTextView?.let{ it.textSize(...) it.setTextColor(...) it.background(..) }
特点
- 函数体内使用it指代当前对象
- 返回值为函数块的最后一行或指定return表达式
- 可以在调用let之前判空处理
with
函数结构
with(object){ //todo }
适用的场景
- 适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法即可(经常用于Android中RecyclerView中onBinderViewHolder中,数据model的属性映射到UI上)
例子
- 不使用with
@Override public void onBindViewHolder(ViewHolder holder, int position) { itemView.shop_tv_edit.setOnClickListener(listener) itemView.shop_tv_insert.setOnClickListener(listener) itemView.shop_tv_show.setOnClickListener(listener) itemView.shop_tv_hide.setOnClickListener(listener) }
- 使用with
@Override public void onBindViewHolder(ViewHolder holder, int position) { with(itemView) { shop_tv_edit.setOnClickListener(listener) shop_tv_insert.setOnClickListener(listener) shop_tv_show.setOnClickListener(listener) shop_tv_hide.setOnClickListener(listener) } }
特点
- 在函数块内可以通过 this 指代当前对象
- 返回值为函数块的最后一行或指定return表达式
run
函数结构
object.run{ //todo }
适用的场景
- 适用于let,with函数任何场景
例子
- 不使用run
override fun onBindViewHolder(holder: ViewHolder, position: Int){ val item = getItem(position)?: return with(item){ holder.tvNewsTitle.text = StringUtils.trimToEmpty(titleEn) holder.tvNewsSummary.text = StringUtils.trimToEmpty(summary) holder.tvExtraInf = "难度:$gradeInfo | 单词数:$length | 读后感: $numReviews" } }
- 使用run
override fun onBindViewHolder(holder: ViewHolder, position: Int){ getItem(position)?.run{ holder.tvNewsTitle.text = StringUtils.trimToEmpty(titleEn) holder.tvNewsSummary.text = StringUtils.trimToEmpty(summary) holder.tvExtraInf = "难度:$gradeInfo | 单词数:$length | 读后感: $numReviews" } }
特点
- 函数体内使用this指代当前对象或者省略
- 返回值为函数块的最后一行或指定return表达式
- 可以在调用run之前判空处理
- run函数是let,with两个函数结合体,适用于let,with函数任何场景。准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理
apply
函数结构
object.apply{ //todo }
适用的场景
- 适用于run函数的任何场景,一般用于初始化一个对象实例的时候,操作对象属性,并最终返回这个对象
- 动态inflate出一个XML的view的时候需要给view绑定数据
- 一般可用于多个扩展函数链式调用
- 数据model多层级包裹判空处理的问题
例子
- 不使用apply
val wgDialog = WGDialog(this@ShopStuffActivity) wgDialog.message = "该页面尚未保存,是否返回?" wgDialog.btnLeft = "取消" wgDialog.btnRight = "确定" wgDialog.addListener(WGDialog.Item.RIGHT, object: WGDialogInterface.OnClickListener{ override fun onButtonClick(which: Int, isCheck: Boolean) { finish() } }) wgDialog.show()
- 使用apply
WGDialog(this@ShopStuffActivity).apply { message = "该页面尚未保存,是否返回?" btnLeft = "取消" btnRight = "确定" addListener(WGDialog.Item.RIGHT, object: WGDialogInterface.OnClickListener{ override fun onButtonClick(which: Int, isCheck: Boolean) { finish() } }) }.show()
特点
- 函数体内使用this指代当前对象或者省略
- 返回的值是对象本身(this)
- 可以在调用apply之前判空处理
also
函数结构
object.also{ //todo }
适用的场景
- 适用于let函数的任何场景,唯一的不同是also返回的值是对象本身(this),一般可用于多个扩展函数链式调用
例子
- 不使用also
mTextView?.textSize(...) mTextView?.setTextColor(...) mTextView?.background(..)
- 使用also
mTextView?.also{ it.textSize(...) it.setTextColor(...) it.background(..) }
特点
- 函数体内使用it指代当前对象
- 返回的值是对象本身(this)
- 可以在调用also之前判空处理
总结