RFR: JDK-8304439: Subscription based listeners [v2]

John Hendrikx jhendrikx at openjdk.org
Fri Jun 16 09:48:10 UTC 2023


On Wed, 14 Jun 2023 19:26:01 GMT, Kevin Rushforth <kcr at openjdk.org> wrote:

>> Yeah I'm aware they want to add this, but it doesn't really invalidate my point as that syntax is terrible still.  Method references are far preferred whenever possible.
>> 
>> I stand by my point that using the observable parameter is a rare and quite advanced use case, where these API's would be intended to make it easier and safer to use method references and lambda's.
>> 
>> When dealing with multiple listeners that require the `Observable`, you are likely also dealing with adding and removing that single listener in some dynamic fashion.  Subscriptions don't work well with this as you'd need to track these, unlike `removeListener` which you can just pass the single listener reference.
>> 
>> Here's one of the only examples in JavaFX that uses the observable parameter (if you know of others, I'd be interested to see those as well):
>> 
>>         private final ChangeListener<Boolean> paneFocusListener = new ChangeListener<>() {
>>             @Override public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
>>                 if (newValue) {
>>                     final ReadOnlyBooleanProperty focusedProperty = (ReadOnlyBooleanProperty) observable;
>>                     final TitledPane tp = (TitledPane) focusedProperty.getBean();
>>                     focus(accordion.getPanes().indexOf(tp));
>>                 }
>>             }
>>         };
>> 
>> And its management code:
>> 
>>         private final ListChangeListener<TitledPane> panesListener = c -> {
>>             while (c.next()) {
>>                 if (c.wasAdded()) {
>>                     for (final TitledPane tp: c.getAddedSubList()) {
>>                         tp.focusedProperty().addListener(paneFocusListener);
>>                     }
>>                 } else if (c.wasRemoved()) {
>>                     for (final TitledPane tp: c.getRemoved()) {
>>                         tp.focusedProperty().removeListener(paneFocusListener);
>>                     }
>>                 }
>>             }
>>         };
>> 
>> To do this with `Subscription::unsubscribe` you'd need to track the `Subscriptions`... something like:
>> 
>>         private final Map<Property<?>, Subscription> subscriptions = new HashMap<>();
>>         private final ListChangeListener<TitledPane> panesListener = c -> {
>>             while (c.next()) {
>>                 if (c.wasAdded()) {
>>                     for (final TitledPane tp: c.getAddedSubList()) {
>>                         subscriptions.put(tp.foc...
>
> I think the three newly added methods are a good choice. I wonder if we can some up with better names, though. without some verb like "add" or "subscribe" in the name, the name doesn't really indicate that it is adding a new listener to the observable.

I agree that the chosen names `invalidation`, `changes` and `values` are a bit terse.  The whole signature (without reading docs) should make it clear you are creating a subscription, but perhaps we can do better.  The use of `addListener` can be ruled out as it would conflict with the existing method due to having Lambda's with the same arity (the `values` listener would conflict with `addListener(InvalidationListener)`.  Also, an `add` method would probably have users expecting a corresponding `remove` method.

A few ideas listed here:

| invalidation | values | changes |
|---|---|---|
|`subscribe(Runnable)`(*)|`subscribe(Consumer)`(*)|`subscribe(BiConsumer)`(*)|
|`subscribeInvalidations(Runnable)`|`subscribeValues(Consumer)`|`subscribeChanges(BiConsumer)`|
|`invalidationsTo(Runnable)`|`valuesTo(Consumer)`|`changesTo(BiConsumer)`|

(*) May limit future listener types that have same arity, but can still be a good choice

-------------

PR Review Comment: https://git.openjdk.org/jfx/pull/1069#discussion_r1232029010


More information about the openjfx-dev mailing list