During the developing sometimes we need develop Kotlin and Java in same project. Some actually behaviour may not same as we think. Here is one example:
The below code is not correct and will cause memory leak.
Here is one Java class:
public class TestWithKotlin {
private Set<Listener> set = new ArraySet<>();
public int getSetSize() {
return set.size();
}
public void addListener(Listener listener) {
set.add(listener);
}
public void removeListener(Listener listener) {
set.remove(listener);
}
public interface Listener {
void onCall();
}
}
And in Kotlin we try to use it:
val testWithKotlin = TestWithKotlin()
val listener = {
// do something here
}
val listener2 = {
// do something here
}
testWithKotlin.addListener(listener)
testWithKotlin.addListener(listener2)
testWithKotlin.removeListener(listener)
testWithKotlin.removeListener(listener2)
println(testWithKotlin.setSize) // the result will be 2, and will cause memory leak and unexpected behaviour here!
Seems the code is correct. But actually removeListener will remove nothing.
Kotlin compiled code will generate a class which implement Listener(KotlinListener). then the object listener and listener2 will pass into this class as a function object.
Actually addListener will add KotlinListener instance. And every time before addListener or removeListener will create a new KotlinListener instance.
So the logic is totally wrong.
Here is the decompiled byteCode of the above kotlin code.
final class TestKotlinKt$sam$com_swifty_playground_TestWithKotlin_Listener$0 implements Listener {
// $FF: synthetic field
private final Function0 function;
TestKotlinKt$sam$com_swifty_playground_TestWithKotlin_Listener$0(Function0 var1) {
this.function = var1;
}
// $FF: synthetic method
public final void onCall() {
Intrinsics.checkExpressionValueIsNotNull(this.function.invoke(), "invoke(...)");
}
}
TestWithKotlin testWithKotlin = new TestWithKotlin();
Function0 listener = (Function0)null.INSTANCE;
Function0 listener2 = (Function0)null.INSTANCE;
Object var10001 = listener;
if (listener != null) {
var10001 = new TestKotlinKt$sam$com_swifty_playground_TestWithKotlin_Listener$0(listener);
}
testWithKotlin.addListener((Listener)var10001);
var10001 = listener2;
if (listener2 != null) {
var10001 = new TestKotlinKt$sam$com_swifty_playground_TestWithKotlin_Listener$0(listener2);
}
testWithKotlin.addListener((Listener)var10001);
var10001 = listener;
if (listener != null) {
var10001 = new TestKotlinKt$sam$com_swifty_playground_TestWithKotlin_Listener$0(listener);
}
testWithKotlin.removeListener((Listener)var10001);
var10001 = listener2;
if (listener2 != null) {
var10001 = new TestKotlinKt$sam$com_swifty_playground_TestWithKotlin_Listener$0(listener2);
}
testWithKotlin.removeListener((Listener)var10001);
int var3 = testWithKotlin.getSetSize();
System.out.println(var3);
The correct code is to declare listener as TestWithKotlin.Listener:
val listener = TestWithKotlin.Listener {
// do something here
}
val listener2 = TestWithKotlin.Listener {
// do something here
}