Kotlin-Java interop: function references and SAM conversions

Though all the things below are pretty obvious if being careful while dealing with Kotlin-Java interop, I decided still to write short note about one particular issue with function references (from Kotlin side) and SAM conversions (from Java side).

Function reference

Function reference is a good way to pass function as a parameter without explicitly using lambdas.

For example, if we have function:

then we can pass our function as a lambda with function call or as a function reference:

I really like to use function references where possible, because it is a bit more concise, you do not create wrapper for “callback”, code is shorter and even easier to read (most of the time).
And this article is about issues function references can produce when they touch Java.

SAM conversions

Just like Java 8, Kotlin supports SAM conversions. This means that Kotlin function literals can be automatically converted into implementations of Java interfaces with a single non-default method, as long as the parameter types of the interface method match the parameter types of the Kotlin function.
Reference

That means that when you call some Java method from Kotlin, and that method satisfies conditions described above, you can pass lambda or method reference instead.

So example (from the same reference):

Issue description

So, let’s take a look at the example, which shows the issue.
Consider we have some ThirdParty Java class with some listeners inside.
One can register some listeners in ThirdParty class and have updates passed through them.
Later on you can unregister listeners.
ThirdParty class might look like this (code with business logic of calculating some data and passing it through listeners is not presented in the code as it doesn’t matter):

So we have Callback interface which satisfied SAM conversion rules, so as a result we can pass lambdas and method references to addCallback and removeCallback methods from Kotlin code.

Then let’s look at the client code.
We will create callback, register it in the ThirdParty class and then immediately unregister it.
After each step we’ll look at the state of ThirdParty class (using logs).

So, here we’ve created callback (we store value in the property, so that we can unregister that callback later).

Let’s look at logs:

So what we see:

  • addCallback was called with one instance of callback and removeCallback was called with another instance (though we passed same function reference to both methods)
  • removeCallback hasn’t removed callback and previously added callback is still registered in ThirdParty. So we have a leak.

That happens because our created callback is a function and is not instance of , so after passing that function reference to a SAM different instanced of are created.

How to fix

To fix this issue (and leak) we should have our callback to be from the beginning and not a function reference.
There are few ways to do that:

All of them are the same, though third one again looks a bit better.

Let’s look at resulting logs:

So all instances are the same and we successfully removed callback from ThirdParty class.

Looks pretty obvious and clear, though such small improvements from Kotlin side to predict how things can be used in Java can provide weird issues which are difficult to track (especially when it comes to memory leaks).

What happens if there is no Java code

One important thing to know is that such issues can happen only between Kotlin and Java.
If we had ThirdParty class written in Kotlin (or just converted from Java to Kotlin), then our previously written code wouldn’t compile:

That’s because SAM conversion works only with Java and not with Kotlin. So in this case we’re pretty much safe and won’t make such errors.

But at the same time we have only one option to create callback:

Other ways won’t work, because SAM conversion is not available and interfaces don’t have constructors.
So there are some drawbacks in readability for the sake of correctness.

Conclusion

The only conclusion from this article is that one should be pretty attentive when dealing with things where Kotlin and Java touch each other.

Android Developer