Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Method Reference Instancing

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?

like image 201
Gene Trog Avatar asked Jan 31 '19 17:01

Gene Trog


People also ask

How do you reference a method using parameters?

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.

How do you reference a static method in Java?

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 do I print a reference method?

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.


1 Answers

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.

like image 64
dpr Avatar answered Oct 13 '22 12:10

dpr