Kotlin session(1)

Function object is not anonymous class

Posted by Swifty Wang on February 13, 2019

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
}