当它们作为 lambda 或方法引用传递时,如何实现可移除的侦听器?

mar*_*kus 9 java design-patterns

我想知道在没有太多接口的情况下在 Java 中实现某种 observable 的好方法是什么。

我认为使用预定义的功能接口会很好。在这个例子中,我使用 aString Consumer来表示一个接受一个字符串作为通知的监听器。

class Subject {

  List<Consumer<String>> listeners = new ArrayList<>();
  
  void addListener(Consumer<String> listener) { listeners.add(listener); }

  void removeListener(Consumer<String> listener { listeners.remove(listener); }

  ...
}

class PrintListener {
  public void print(String s) { System.out.println(s); }
}

Subject subject = new ...
PrintListener printListener = new ...
subject.add(printListener); // Works, I find it in the listener list
subject.remove(printListener); // Does NOT work. I still find it in the list

Run Code Online (Sandbox Code Playgroud)

我找到了解释:

Consumer<String> a = printListener::print;
Consumer<String> b = printListener::print;

// it holds:
// a==b       : false
// a==a       : true
// a.equals(b): false
// a.equals(a): true
Run Code Online (Sandbox Code Playgroud)

所以我不能按原样使用 lambdas/函数指针。

总有一种选择可以恢复旧的接口,我们注册对象实例而不是 lambdas。但我希望有更轻量的东西。

编辑:

从目前的回应中,我看到以下方法:

a) 返回Handle保存原始引用的
a b) 自己存储原始引用
c) 返回一些可用于subject.remove()代替原始引用的ID(整数)

我倾向于喜欢 a)。您仍然必须跟踪Handle.

Lin*_*ica 7

I'm using rjxs quite often lately, and there they've used a custom return value called Subscription which can be called to remove the registered listener again. The same could be done in your case:

public interface Subscription {
    void unsubscribe();
}
Run Code Online (Sandbox Code Playgroud)

Then change your addListener method to this:

public Subscription addListener(Consumer<String> listener) {
    listeners.add(listener);
    return () -> listeners.remove(listener);
}
Run Code Online (Sandbox Code Playgroud)

The removeListener method can be removed entirely. And this can now be called like this:

Subscription s = subject.addListener(printListener::print);
// later on when you want to remove the listener
s.unsubscribe();
Run Code Online (Sandbox Code Playgroud)

This works, because the returned lambda in addListener() still uses the same reference of listener and thus can again be removed from the List. Side note: it would probably make more sense to use a Set unless you really care about the iteration order of your listeners

A nice read would also be Is there a way to compare lambdas?, which goes into more detail, why printListener::print != printListener::print.


Kay*_*man 6

Your assumption is that each call to printListener::print returns the same instance of print.

Subject subject = new Subject();
PrintListener printListener= new PrintListener();
subject.add(printListener::print);
subject.remove(printListener::print);
Run Code Online (Sandbox Code Playgroud)

The above code adds one listener, and tries to remove another listener, since printListener::print.equals(printListener::print) == false.

Subject subject = new Subject();
PrintListener printListener= new PrintListener();
Consumer<String> listener = printListener::print;
subject.add(listener);
subject.remove(listener);
Run Code Online (Sandbox Code Playgroud)

The above works, requiring you to keep a reference to the listener if you need to remove it. Although if you wanted to keep it very lightweight, you could stick to only Consumers without the concrete PrintListener class, if the implementations are very simple.

Consumer<String> listener = System.out::println;
subject.add(listener);
subject.remove(listener);
Run Code Online (Sandbox Code Playgroud)