Is it safe to use function references to register and unregister a listener?

Viewed 183

I recently found myself writing the following code:

fun listener() {
    // Do some stuff
    adapter.removeLoadStateListener(::listener)
}
adapter.addLoadStateListener(::listener)

A colleague remarked on the fact that

val x1 = ::listener
val x2 = ::listener
x1 == x2 //true
x1 === x2 //false

However,

var mySet = mutableSetOf<() -> Unit>()

fun a() { }

fun b() { }

mySet.add(::a)
mySet.add(::b)
mySet.remove(::a)

mySet.contains(::a) // false
mySet.contains(::b) // true

Based on this I get the impression I do not understand ::myFun properly and I start to question if my original code is safe to use.

TL;DR

  1. Is it safe to use ::listener to reference a method to be used as a listener and that will need to be referenced multiple times (e.g. add + remove)?
  2. What is actually going on behind the scenes regarding anonymous classes etc.?

EDIT

Eventually we decided against using ::listener for this case. However, we ran into a new problem since add/removeLoadStateListener will only accept the (CombinedLoadState) -> Unit type.

I'll leave our solution to that problem here for future reference and other readers as it is related (I expect some may even come here looking for this rather than answers to the original question):

val listener = object : (CombinedLoadStates) -> Unit {
    override operator fun invoke(loadState: CombinedLoadStates) {
        // Do some stuff
        adapter.removeLoadStateListener(this)
    }
}
adapter.addLoadStateListener(listener)
1 Answers

When you use ::listener, you are sort of creating an anonymous class that implements the interface, each time. However, as your test shows, the .equals of the anonymous classes will return that they are equal, which usually wouldn't be the case with an anonymous class (created with object: syntax). So, two instances created using ::listener will be equivalent with == but not with ===.

Sets typically use .equals equality (==) to determine if an instance is a duplicate, but it's possible to create a Set backed by IdentityHashMap so it effectively behaves as a Set using identity comparison. This breaks the Set contract, but a class could be using it internally for some reason.

Whether it is safe to use this depends on whether the class you're working with compares listener instances by .equals or identity. If it's possible it's using an IdentityHashMap to store and compare listeners, then this is not safe.

Related