匿名内部类在 Java 中是经常用到的一个特性,例如在 Android 开发中的各种 Listener,使用时也很简单,比如:
1 2 3 4 5 6 7 8 9 10 11 12
|
button.setOnClickListener(v -> { });
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { } });
|
只有一个函数的接口在 Java 和 Kotlin 中都可以很方便的使用 lambda 表达式来缩略,但是如果接口含有多个函数,使用起来就比较”不优雅“了,例如:
1 2 3 4 5 6 7 8 9 10 11
| etString.addTextChangedListener(object :TextWatcher{ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { TODO("Not yet implemented") } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { TODO("Not yet implemented") } override fun afterTextChanged(s: Editable?) { TODO("Not yet implemented") } })
|
使用起来与 Java 基本差不多,通过 object 关键字实现了一个匿名内部类,这种方法没什么大问题,例如上面的例子中,三个回调函数并非每次都要使用,很多场景可能只会用到其中一个或者几个,其余的都是空实现,每次都写这样一个匿名内部类只不过是不优雅而已。
在 Kotlin 中我们可以有两种方式实现比较优雅的使用匿名内部类:
DSL
DSL 方式实现封装可以分为以下几步:
1.创建接口实现类:XxxxInterfaceDslImpl
还有上面的 TextWatcher 作为例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class TextWatcherDslImpl : TextWatcher {
private var afterTextChanged: ((Editable?) -> Unit)? = null
private var beforeTextChanged: ((CharSequence?, Int, Int, Int) -> Unit)? = null
private var onTextChanged: ((CharSequence?, Int, Int, Int) -> Unit)? = null
fun afterTextChanged(method: (Editable?) -> Unit) { afterTextChanged = method }
fun beforeTextChanged(method: (CharSequence?, Int, Int, Int) -> Unit) { beforeTextChanged = method }
fun onTextChanged(method: (CharSequence?, Int, Int, Int) -> Unit) { onTextChanged = method }
override fun afterTextChanged(s: Editable?) { afterTextChanged?.invoke(s) }
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { beforeTextChanged?.invoke(s, start, count, after) }
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { onTextChanged?.invoke(s, start, before, count) } }
|
这个实现类由三个部分组成:
- 原接口方法对应的 Kotlin 函数对象,函数对象的签名与对应的方法签名保持一致
- DSL 函数,函数名称、签名都与原接口的方法一一对应,用于接收 lambda 赋值给 Kotlin 函数对象
- 原接口方法的实现,每个接口方法的实现,都是对实现类中 Kotlin 函数对象的调用
2.创建与原函数同名的扩展函数,函数参数为实现类扩展函数
1 2 3 4 5
| fun TextView.addTextChangedListenerDsl(init: TextWatcherDslImpl.() -> Unit) { val listener = TextWatcherDslImpl() listener.init() this.addTextChangedListener(listener) }
|
扩展函数与原函数同名可以方便使用者调用,无需记忆其他函数名,如果担心混淆,可以在函数名后加上 Dsl
用以区分。该函数的参数是我们第一步创建的实现类的扩展函数,这是为了实现 DSL 语法。
3.使用
1 2 3 4 5 6 7
| etString.addTextChangedListenerDsl { afterTextChanged { if (it.toString().length >= 4) { KeyboardUtils.toggleSoftInput() } } }
|
使用这种方式时,可以说相当之优雅,我们只需要调用我们需要实现的接口方法即可,不需要使用的接口方法默认空实现。
高阶函数
高阶函数方式比 DSL 方式更简单一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| inline fun TextView.addTextChangedListenerClosure( crossinline afterTextChanged: (Editable?) -> Unit = {}, crossinline beforeTextChanged: (CharSequence?, Int, Int, Int) -> Unit = { charSequence, start, count, after -> }, crossinline onTextChanged: (CharSequence?, Int, Int, Int) -> Unit = { charSequence, start, after, count -> } ) { val listener = object : TextWatcher { override fun afterTextChanged(s: Editable?) { afterTextChanged.invoke(s) }
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { beforeTextChanged.invoke(s, start, count, after) }
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { onTextChanged.invoke(s, start, before, count) } } this.addTextChangedListener(listener) }
|
我们创建一个同名扩展函数,使用 Closure
尾缀作为区分,该函数的参数为与接口方法一一对应的 Kotlin 函数对象,并给其默认值赋值为 {}
即空实现,在函数体里通过 object 关键字构建匿名内部类实现对象,在其接口方法实现中调用与之一一对应的 Kotlin 函数对象。
使用方式上与普通的 Kotlin 高阶函数使用方式相同:
1 2 3 4 5 6 7
| etString.addTextChangedListenerClosure( afterTextChanged = { if (it.toString().length >= 4) { KeyboardUtils.toggleSoftInput() } }, )
|
PS:此段代码存在官方实现,在core-ktx包中 **[androidx.core.widget.addTextChangedListener]**,官方实现还额外添加了单参数的扩展函数,此处感谢 Goooler 的提醒。
tips:
上面示例的扩展函数中,我们使用了 inline 与 crossinline 两个关键字,这是 Koltin 特有的。inline 关键字通常用于修饰高阶函数,用于提升性能。crossinline 声明的 lambda 不允许局部返回,用于避免调用者错误的使用 return 导致函数中断。
提供一个示例代码,亲自尝试一下也许可以更好的理解:
1 2 3 4 5 6 7 8 9 10 11
| @Test fun testInline() { testClosure { return } } private inline fun testClosure(test: (String) -> String ) { println("step 1") println(test("step test")) println("step 2") }
|