I'm trying to build a library where you can add and remove listeners for events in a pub/sub system, but am running into an issue using method references:
// here, this::printMessage is being passed as an instance of Consumer<String>
pubSub.subscribe(this::printMessage);
pubSub.unsubscribe(this::printMessage);
Internally, calling subscribe()
will add the instance of Consumer<T>
to a Set<Consumer<T>>
, and unsubscribe()
will remove it. This issue arises from the fact that each usage of this::printMessage
here actually causes the compiler to generate a new object reference/instance, so, unsubscribing doesn't actually work.
The workaround so far that I've managed is:
final Consumer<String> consumer = this::printMessage;
pubSub.subscribe(consumer);
pubSub.unsubscribe(consumer);
But, this isn't really ideal. My concern is someone less-experienced using this library may assume that they can use method references directly when subscribing/unsubscribing, when that's not really the case, and worst case, leading to a memory leak.
So the question is, is there some clever way to avoid this or coerce the method reference to always resolve to the same object reference/instance?
To make the code clearer, you can turn that lambda expression into a method reference: Consumer<String> c = System. out::println; In a method reference, you place the object (or class) that contains the method before the :: operator and the name of the method after it without arguments.
References to static methods Its syntax is className::staticMethodName , where className identifies the class and staticMethodName identifies the static method. An example is Integer::bitCount .
How to print the values using method reference? in method references you just give the name of the function (println), they don't take arguments. You can create your own function that accepts a string and calls toUpperCase and then println, and then give the name of your function as the method reference name.
You could make subscribe
either return the actual Consumer
instance or an identifier for the added Consumer
. This return value could be used in unsubscribe
to remove the Consumer
again.
Maybe something similar to this:
Map<UUID, Consumer<?>> consumers = new ConcurrentHashMap<>();
public UUID subscribe(Consumer<?> consumer) {
UUID identifier = UUID.randomUUID();
consumers.put(identifier, consumer);
return identifier;
}
public void unsubscribe(UUID identifier) {
consumers.remove(identifier);
}
The usage of an identifier instead of the actual Consumer
instance as return value has the advantage that users of your code will directly see that they need to keep track of the returned UUID instead of using unsubscribe
with a different 'identical' (in terms of behavior) Consumer
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With