介绍
这里记录一下使用Kotlin中遇到的一些问题。
Lambda问题
我们在开发App的时候经常会使用观察者模式订阅某个主题,当主题发生变化的时候通知所有观察者,但是在Java中定义观察者的逻辑后,在Kotlin中调用Java的时候就会出现奇怪的问题。比如:
public class CurrentLocationProvider {
private CurrentLocationProvider(){}
private static class Holder{
private static CurrentLocationProvider instance = new CurrentLocationProvider();
}
public static CurrentLocationProvider getInstance(){
return Holder.instance;
}
private List<LocationChangeListener> locationChangeListeners = new ArrayList<>();
public void addListener(LocationChangeListener listener){
locationChangeListeners.add(listener);
System.out.println("添加的listener是"+listener+",添加后的集合长度是:"+locationChangeListeners.size());
}
public void removeListener(LocationChangeListener listener){
locationChangeListeners.remove(listener);
System.out.println("移除的listener是"+listener+",移除后的集合长度是:"+locationChangeListeners.size());
}
public interface LocationChangeListener{
void onLocationChange();
}
}
定义一个全局的单例,需要监听位置变化的时候添加监听器,在activity退出的时候要记得移除掉,否则会内存泄漏。那么我们在Kotlin中怎么使用呢?
fun main() {
val currentLocationProvider = CurrentLocationProvider.getInstance()
val listener={
}
currentLocationProvider.addListener(listener)
currentLocationProvider.addListener(listener)
currentLocationProvider.addListener(listener)
println()
currentLocationProvider.removeListener(listener)
currentLocationProvider.removeListener(listener)
currentLocationProvider.removeListener(listener)
}
因为Kotlin会把Java中单个抽象函数的接口作为Lambda使用,所以这里定义一个常量listener,那么结果是怎么样的呢?看下图
结果和我们预想的完全不一样,从结果可以看出每一次的add和remove都是一个新的listener对象,真的是这样吗?我们看一下Kotlin的字节码。
...
LINENUMBER 13 L1
GETSTATIC com/example/kotlin/sam/SelectPoiKt$main$listener$1.INSTANCE : Lcom/example/kotlin/sam/SelectPoiKt$main$listener$1;
CHECKCAST kotlin/jvm/functions/Function0
ASTORE 1
...
这一段是我们定义的listener常量的字节码,这里可以看出listener是一个Function0的实例。
我们看一下add和remove的字节码
从图中可以看出每次都会new一个匿名实例实现LocationChangeListener接口,并且在new 的时候讲Function0的实例即我们定义的listener传递进去,然后在add的时候进行类型转换讲匿名实例强转成LocationChangeListener。remove的字节码也是这样实现的。
所以我们在每次add和remove的时候都是new一个新的实例。
那么如果一定要使用Kotlin调用,怎么解决这个问题了,其实也很简单,我们只要显示的声明的实例就可以了,比如:
fun main() {
val currentLocationProvider = CurrentLocationProvider.getInstance()
val listener=object:CurrentLocationProvider.LocationChangeListener{
override fun onLocationChange() {
}
}
......
}
此时我们在run运行一下就可以看到正确的结果了。
总结
这个地方我们在进行Java和Kotlin混合开发的时候要注意一下。
集合框架的混合开发的问题
我们都知道在Kotlin中的集合分为两种:只读和可读写的(MutableXXX),所以我们在进行混合开发的时候也要注意这一点,比如:
object TestKotlinCollections{
val list = listOf(1,2,3)
}
Kotlin定义一个单例,和一个变量list。注意这里返回是List的实例,其中List是只读的。我们在java中一不小心就会写出下边的代码:
public class JavaTestCollections {
@Test
public void testCollections(){
//这里的list是只读的不可以写入
List<Integer> list = TestKotlinCollections.INSTANCE.getList();
list.add(4);
}
}
这段代码运行的时候回提示你不支持操作异常。
总结
在混合开发的时候要注意Kotlin的代码返回集合是只读的还是可读写的?