记录 Kotlin 实践的一些好建议

945 阅读2分钟

目录

  1. 注释
  2. 函数式接口
  3. 高阶函数
  4. 扩展函数

注释

Java:

    /**
     * @see AdVideoUserInfoContainerData#type
     */
    public Builder type(int type) {
        userInfoData.type = type;
        return this;
    }
    /** 事件配置, 对应于 {@link FeedAdLottieRepoInfo#name 属性} */
    public String lottieConfig;

Kotlin:

/**
 * 由[CountDownType.type] mapTo [CountDownType]
 * 避免了使用 when(type) 写 else 了
 */
private fun type2Enum(type: () -> String): CountDownType {
    return CountDownType.values().firstOrNull {
        it.type == type()
    } ?: CountDownType.CIRCLE
}

Kotlin 可以使用内联标记来引用类、方法、属性等,这比 Java 中的 @see、@link 更加易用。

文档:kotlinlang.org/docs/kotlin…

函数式接口

非函数式接口:

internal interface ICountDownCallback {
    /**
     * 倒计时完成时回调
     */
    fun finish()
}
​
internal fun setCountDownCallback(callback: ICountDownCallback) {
    // ignore
}
​
internal fun show() {
    setCountDownCallback(object : ICountDownCallback {
        override fun finish() {
            TODO("Not yet implemented")
        }
    })
}

函数式接口:

internal fun interface ICountDownCallback {
    /**
     * 倒计时完成时回调
     */
    fun finish()
}
​
internal fun setCountDownCallback(callback: ICountDownCallback) {
    // ignore
}
​
internal fun show() {
    setCountDownCallback { 
        TODO("Not yet implemented")
    }
}

函数式接口也被称为单一抽象方法(SAM)接口,使用函数式接口可以使代码更加简洁,富有表现力。

对于 Java 的接口,比如 View.OnClickListener,它在使用的时候可以直接转 lambda 使用的,只有是 kotlin 的单一抽象方法,需要加 fun 关键字标示它为函数式接口。

文档:kotlinlang.org/docs/fun-in…

高阶函数

如果对象的初始化比较麻烦,可以使用高阶函数,让代码更加流畅:

    // 定义
    open fun attachToViewGroup(
        viewGroup: ViewGroup,
        index: Int = -1,
        lp: () -> MarginLayoutParams = {
            MarginLayoutParams(
                LayoutParams.WRAP_CONTENT,
                LayoutParams.WRAP_CONTENT
            )
        }
    ) {
        (this.parent as? ViewGroup)?.removeView(this)
        viewGroup.addView(this, lp.invoke())
    }
​
    // 使用
    override fun attachToViewGroup(viewGroup: ViewGroup, index: Int, lp: () -> MarginLayoutParams) {
        super.attachToViewGroup(viewGroup, index) {
            MarginLayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
            ).apply {
                leftMargin = 14.px(context)
                topMargin = 44.px(context)
            }
        }
    }

如果参数的获取比较复杂,代码比较长,有不少判断逻辑,也可以使用高阶函数:

// 定义
fun getCountDownViewByType(context: Context, type: () -> String = { "0" }) {
    // ignore
}
// 使用
countDownView = CountDownType.getCountDownViewByType(this) {
    rewardVideoCmdData.cmdPolicyData?.countDownType ?: ""
}

如果方法的返回值是一个状态值,然后根据状态值去做相关逻辑处理。这种情况下,其实我们想要的是一个行为,比如代码中充斥着大量的数据解析、校验等逻辑,我们也可以是使用高阶函数重构:

// 重构之前
/**
 * 校验数据有效(校验标题和按钮有一个不为空,就可以展示 Dialog)
 */
fun checkValid(): Boolean {
    return !dialogTitle.isNullOrEmpty() || !buttonList.isNullOrEmpty()
}
​
private fun bindData() {
    rewardData = RewardDialogData(arguments?.getString(EXTRA_REWARD_DATA) ?: "")
    // 弹窗数据不合法,就不需要展示 dialog 了
    if (rewardData == null || !rewardData!!.checkValid()) {
        dismiss()
        return
    }
    // 更新字体颜色等
    updateSkin()
}
​
 
// 重构之后
/**
 * 数据校验失败,执行 [fail] 函数
 */
internal inline fun RewardDialogData?.checkFailed(fail: () -> Unit) {
    this?.let {
        if (dialogTitle.isNullOrEmpty() && buttonList.isNullOrEmpty()) {
            fail()
        }
    } ?: fail()
}
 
​
private fun bindData() {
    rewardData = RewardDialogData(arguments?.getString(EXTRA_REWARD_DATA) ?: "")
    // 弹窗数据不合法,就不需要展示 dialog 了
    rewardData?.checkFailed {
        dismiss()
        return
    }
    // 更新字体颜色等
    updateSkin()
}

kotlin 标准库里面也是有非常多的高阶函数的,比如作用域函数(let、apply、run等等),除此之外,还有一些集合类的标准库函数:

// filter
fun showCharge() {
    adMonitorUrl?.filter {
        !it.showUrl.isNullOrEmpty()
    }?.forEach {
        ParallelCharge.charge(it.showUrl)
    }
}
// forEachIndexed
list.forEachIndexed { index, i ->
    // ignore
}

文档:kotlinlang.org/docs/lambda…

扩展函数

// 比较不流畅的写法
val topImgUrl = rewardData?.topImg
if (topImgUrl.isNullOrBlank()) {
    topImg.visibility = View.GONE
} else {
    topImg.hierarchy?.useGlobalColorFilter = false
    topImg.visibility = View.VISIBLE
    topImg.setImageURI(topImgUrl)
}
// 使用局部返回标签
topImg.apply {
    if (topImgUrl.isNullOrEmpty()) {
        visibility = View.GONE
        return@apply
    }
    hierarchy?.useGlobalColorFilter = false
    setImageURI(topImgUrl)
    visibility = View.VISIBLE
}
/**
 * 校验 View 可见性
 *
 * @return [predicate] false: GONE;true: VISIBLE
 */
internal inline fun <reified T : View> T.checkVisible(predicate: () -> Boolean): T? {
    return if (predicate()) {
        visibility = View.VISIBLE
        this
    } else {
        visibility = View.GONE
        null
    }
}
​
// 使用扩展函数
topImg.checkVisible {
    !topImgUrl.isNullOrEmpty()
}?.run {
    hierarchy?.useGlobalColorFilter = false
    setImageURI(topImgUrl)
}

文档:kotlinlang.org/docs/extens…

\