共计 3189 个字符,预计需要花费 8 分钟才能阅读完成。
前言
最近看到 DSL 这个货色,不禁的感觉外面能够利用 Kotlin 的一些个性能简化代码,所以具体来看看它是如何实现的。
注释
首先一上来就说原理或者对于不相熟 Kotlin 的来说会感觉有点突兀,所以我筹备从头梳理一下。
约定
Kotlin 的约定咱们在平时开发中必定用到过,不过咱们没有认真去留神这个名词而已。约定的概念就是:应用与惯例办法调用语法不同的、更简洁的符号,调用着有着非凡命名的函数。
这里提取 2 个关键点,一个是更简洁的符号调用,一个是非凡命名的函数。说白了就是让函数调用更加简洁。
比方咱们最相熟的集和调用 [index] 来 代替 get(index),咱们本人也来定义个类,来实现一下这个约定:
data class TestBean(val name: String,val age: Int){
// 定义非常简单 应用 operator 重载运算符 get 办法
operator fun get(index : Int): Any{return when(index) {
0 -> name
1 -> age
else -> name
}
}
}
而后咱们在应用时:
// 这里就能够应用 [] 来替换 get 来简化调用办法了
val testBean = TestBean("zyh",20)
testBean.get(0)
testBean[0]
invoke 约定
和下面的 get 约定一样,[] 就是调用 get 办法的更简洁的形式,这里有个 invoke 约定,它的作用就是让对象像函数一样调用办法,上面间接来个例子:
data class TestBean(val name: String,val age: Int){
// 重载定义 invoke 办法
operator fun invoke() : String{return "$name - $age"}
}
定义完下面代码后,咱们来进行应用:
val testBean = TestBean("zyh",20)
// 失常调用
testBean.invoke()
// 约定后的简化调用
testBean()
这里会发现 testBean 对象能够调用 invoke 办法是失常调用,然而也能够 testBean() 间接来调用 invoke 办法,这就是 invoke 约定的作用,让调用 invoke 办法更简略。
invoke 约定和函数式类型
既然理解了 invoke 约定,咱们来和 lambda 联合起来。
咱们晓得函数类型其实就是实现了 FunctionN 接口的类,而后当函数类型是函数类型时,这时传递给它一个 lambda,lambda 就会被编译成 FunctionN 的匿名外部类 (当然是非内联的),而后调用 lambda 就变成了一次 FunctionN 接口的 invoke 调用。
还是看个例子代码:
// 定义代码
class TestInvoke {
// 高阶函数类型变量
private var mSingleListener: ((Int) -> Unit)? = null
// 设置变量
public fun setSingleListener(listener:((Int) -> Unit)?){this.mSingleListener = listener}
//
fun testRun() {
// 调用 invoke 函数
mSingleListener?.invoke(100)
// 应用 invoke 约定,省去 invoke
if (mSingleListener != null){mSingleListener!!(100)
}
}
}
定义完下面回调变量后,咱们来应用这个回调,因为咱们晓得高阶函数其实是实现了 FunctionN 接口的类,也就是实现了:
// 留神,这里接口的办法就是 invoke
public interface Function1<in P1, out R> : Function<R> {
/** Invokes the function with the specified argument. */
public operator fun invoke(p1: P1): R
}
那我也就能够间接应用上面代码来传递参数:
val function1 = object: Function1<Int,Unit> {override fun invoke(p1: Int) {Logger.d("$p1")
}
}
testInvoke.setSingleListener(function1)
这里看起来荒诞不经,因为在 testRun 函数中咱们调用了 invoke 函数,把 100 当做参数,而后这个 100 会被回调到 function1 中,然而咱们传递 lambda 时呢:
val testInvoke = TestInvoke()
testInvoke.setSingleListener { returnInt ->
Logger.d("$returnInt")
}
下面代码传递 lambda 和传递一个类的实例成果是一样的,只不过这里只是一段代码块,没有显示的调用 invoke 啥的,所以这就是一个个性, 当 lambda 被用作参数被函数调用时,也就能够看成是一次 invoke 的主动调用 。
invoke 在 DSL 中的实际:Gradle 依赖
这里咱们为什么要说这个 invoke 依赖呢,很大的起因就是它在一些 DSL 中有很好的用法,这里咱们就来看个 Gradle 依赖的应用。
咱们很常见上面代码:
dependencies {
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
//...
}
这里咱们都很司空见惯,感觉这里很像配置项,而不像是代码,其实这个也是一段代码,只不过是这种格调。那这种格调如何实现呢,咱们来简略实现一下:
class DependencyHandler{
// 编译库
fun compile(libString: String){Logger.d("add $libString")
}
// 定义 invoke 办法
operator fun invoke(body: DependencyHandler.() -> Unit){body()
}
}
下面代码写完后,咱们便能够有上面 3 种调用形式:
val dependency = DependencyHandler()
// 调用 invoke
dependency.invoke {compile("androidx.core:core-ktx:1.6.0")
}
// 间接调用
dependency.compile("androidx.core:core-ktx:1.6.0")
// 带接受者 lambda 形式
dependency{compile("androidx.core:core-ktx:1.6.0")
}
由此可见,下面代码第三种形式便是咱们在 Gradle 配置文件中常见的一种,这里其实就 2 个关键点,一个是定义 invoke 函数,一个是定义带接受者的 lambda,调用时省去 this 即可。
总结
其实对于 invoke 约定和带接受者 lambda 的写法当初越来越风行了,比方之前的 anko 库,当初的 compose 库都是这种申明式的写法,看完原理后,就会发现其实还是很不便的。
后续开始钻研 compose 的时候,再来补充一波。
相干教程
Android 根底系列教程:
Android 根底课程 U - 小结_哔哩哔哩_bilibili
Android 根底课程 UI- 布局_哔哩哔哩_bilibili
Android 根底课程 UI- 控件_哔哩哔哩_bilibili
Android 根底课程 UI- 动画_哔哩哔哩_bilibili
Android 根底课程 -activity 的应用_哔哩哔哩_bilibili
Android 根底课程 -Fragment 应用办法_哔哩哔哩_bilibili
Android 根底课程 - 热修复 / 热更新技术原理_哔哩哔哩_bilibili
本文转自 https://juejin.cn/post/7047028786969346079,如有侵权,请分割删除。