整理了自己在实际开发中关于kotlin的学习和思考:深入学习Kotlin,感兴趣的可以查看,后续会不断地更新。
函数式接口
函数式接口,英文名称(Functional (SAM) interfaces)
,全称是 Single Abstract Method (SAM) interface
。在 Kotlin 中,仅具有一个抽象方法的接口称为函数式接口或单一抽象方法(SAM)接口。函数式接口可以有多个非抽象成员,但只能有一个抽象成员。比如下面的接口:
kotlin
fun interface IServiceInterface {
fun getService()
}
很多人看到这会觉得这个不是和普通的接口一样吗?是的,我开始的时候,也是这么认为,这不都一样的吗?但是你仔细看,就可以看到接口定义的时候,多了一个fun
关键字。是的,函数式接口在定义的时候,多一个了fun
关键字修饰,如果没有,那这个接口就是一个普通的接口。
作用
那么函数式接口有什么作用?其实最大的作用就是:可以做 ASM 转化,即把接口实现转为lambda
实现。
我们知道,在 kotlin 中,对于 Java 定义的SAM
接口,会默认当作函数式接口。比如常用的设置View
的click
事件,我们正常可以类似 Java 的写法:
kotlin
view.setOnClickListener(object : OnClickListener {
override fun onClick(v: View?) {
TODO("Not yet implemented")
}
})
使用lambda
实现的方式,如下面:
kotlin
view.setOnClickListener {
TODO("Not yet implemented")
}
可以看到,直接使用lambda
的方式实现这个接口,写起非常的方便。
这是针对 Java 接口默认的操做。但是对于 kotlin 中定义的接口,默认是没有这种写法的,即使这个接口是一个SAM
接口,比如下面这个接口:
kotlin
interface IServiceInterface {
fun getService()
}
他在实现的时候,就只能通过object
创建匿名对象的方式实现调用
kotlin
setIServiceInterface(object : IServiceInterface {
override fun getService() {
print("定义的接口")
}
})
那如果要实现 lambda 的调用方式,就需要定义函数式接口,也就是:
kotlin
fun interface IServiceInterface {
fun getService()
}
这样的话,就能够实现
kotlin
setIServiceInterface{
print("定义的接口")
}
Android 源码中也定义了好多类似的接口,比如我们常用的Observer
接口
kotlin
fun interface Observer<T> {
/**
* Called when the data is changed is changed to [value].
*/
fun onChanged(value: T)
}
实际使用中的坑
函数式接口在使用的时候确实很方便,但是使用不当或者不理解背后的原理,很可能会埋下坑,觉得逻辑很奇怪。
举个不太恰当的例子(仅仅是用来说明问题):
kotlin
// ViewModel请求数据,假设返回的值为:七郎
viewModel.requestData()
findViewById<ViewGroup>(R.id.tv1).apply {
setOnClickListener {
// 每次点击,就注册观察者,接收数据
viewModel?.liveData?.observe(this@MainActivity) {
// 打印获取到的数据,假设就是就是上面的:七郎
Log.i("MainActivity", "return result is ${it}")
}
}
}
上面的例子中,先是 ViewModel
获取数据,然后每次点击文字控件tv1
就注册一个观察者,监听数据回调。正常按照我的理解,每点击一次打印一次日志return result is 七郎
。因为在点击之前已经加载了数据,按照 LiveData 的特性,后面注册的观察者,都会接收到一次上次的数据。但是实际上整个日志就打印了一次。
那这是为什么呢?我们反编译下 apk 的代码:
java
public static final void onCreate$lambda$1$lambda$0(MainActivity this$0, View it) {
PictorialLiveData<String> liveData;
TestViewModel testViewModel = this$0.viewModel;
// 注释1
liveData.observe(this$0, new MainActivity$sam$androidx_lifecycle_Observer$0(MainActivity$onCreate$1$1$1.INSTANCE));
}
可以看到注释1
的位置,调用liveData
的observe
方法,其中的Observer
接口是编译器自动生成的一个类MainActivity$sam$androidx_lifecycle_Observer$0
,这个类实现了Observer
接口,如下面:
java
public final class MainActivity$sam$androidx_lifecycle_Observer$0 implements Observer, FunctionAdapter {
private final /* synthetic */ Function1 function;
public MainActivity$sam$androidx_lifecycle_Observer$0(Function1 function) {
Intrinsics.checkNotNullParameter(function, "function");
this.function = function;
}
......
}
其中的Function1
就是我们上面 kotlin 代码中的打印日志的业务逻辑实现,kotlin 会自动根据参数的个数编译成Function1,Function2....FunctionN
,也就是上面的MainActivity$onCreate$1$1$1.INSTANCE
变量,我们看下它的实现:
java
public final class MainActivity$onCreate$1$1$1 extends Lambda implements Function1<String, Unit> {
// !!!!!!!看到没有,这里是静态变量
public static final MainActivity$onCreate$1$1$1 INSTANCE = new MainActivity$onCreate$1$1$1();
......
public final void invoke2(String it) {
Intrinsics.checkNotNullParameter(it, "it");
Log.i("MainActivity", "return result is " + it);
}
}
当你看到这个类的时候,估计你就明白为什么上面的日志只打印一次了,因为 kotlin 中每个函数式接口(如果没有引用外部的非静态变量或者对象,这里存在差异),对应 lambda 表达式编译成立一个静态变量,也就是不管你调用了 observe 多少次,他都是同一个对象
所以,在了解了背后的原理之后,我们在使用这种函数式接口的 lambda 方式时要非常注意,涉及到静态变量,稍微不小心,就有可能造成内存泄漏或者逻辑错误。
如果觉得对你有帮助,请点赞关注,或者关注我交流。也可以点击深入学习Kotlin看其他文章,希望一起学习进步,加油!