On Tue, 29 Sep 2026 22:52:58 GMT, Kevin Rushforth <[email protected]> wrote:

>> John Hendrikx has updated the pull request incrementally with one additional 
>> commit since the last revision:
>> 
>>   Update documentation according to review comments
>
> modules/javafx.base/src/main/java/javafx/beans/value/ObservableListValue.java 
> line 35:
> 
>> 33:  * @implNote
>> 34:  * The implementations of this interface in the JavaFX library do not 
>> provide all
>> 35:  * of the guarantees described by {@link ObservableValue} for change 
>> listeners: the
> 
> In addition to the ones listed below, can you add that equal values may 
> trigger a notification? That guarantee from [ObservableValue lines 
> 97-99](https://github.com/hjohn/jfx/blob/871cbc31695403dd2f9bfaaaa5353478a0d8fb33/modules/javafx.base/src/main/java/javafx/beans/value/ObservableValue.java#L97-L99)
>  doesn't hold. This also applies to the observable map and set value 
> interfaces.

On the `equals` point, I think it's the same underlying issue as @mstr2 raised 
above:

> I think that invoking a change listener when only the contents of a list has 
> changed is a jarring defect. A change listener should only be invoked when 
> the list instance is changed; for its contents, we have ListChangeListener. 
> Now, I'm not sure if we can ever fix this, but maybe we shouldn't enshrine 
> this defect into specification with this PR. If we come to the conclusion 
> that it can't be changed, we can always specify this behavior later.

I think this may be a longer discussion, as there are several issues with how 
change listeners work for these that deviate strongly from how they work for 
properties:

| Case | Property / binding `ChangeListener` | `ObservableListValue` 
`ChangeListener` |
|---|---|---|
| `set(x)` where `x == old` (same instance) | no notification (`set` no-op) | 
no notification (`set` no-op) |
| `set(x)` where `x != old` but `x.equals(old)` | **no notification** (the 
manager suppresses equal values) | **notification**, with `old.equals(new)` and 
`old != new` |
| `set(x)` where `x != old` and `!x.equals(old)` | notification (`old`, `new`) 
| notification (`old`, `new`) |
| contents change (same list instance) | n/a | **notification**, with `old == 
new` (identical instance) |

These are understandable choices, but it may have been better not to reuse the 
same `ChangeListener` interface given how they deviate (especially a reference 
change, but still being equal). I'd therefore hold off on specifying this 
behaviour for now, as @mstr2 suggested -- **however** I think it truly can't be 
fixed; using a change listener for lists to indicate reference changes is 
exactly what property listeners **don't** do -- so I think I still stand by my 
stance that they should be deprecated for the collection value types and one 
should use `InvalidationListener` + `getValue()` if they want the old 
`ChangeListener` behavior, or use the true API: `ListChangeListener`.

Going a bit wider, this is worth getting right because change listeners (for 
properties) intentionally don't notify when `equals()` (which seems sensible), 
but that does open you up for "large object is replaced with another large but 
`equals()` object" and holding on to the old reference since a change listener 
doesn't get notified then. For `String`s this is already the case, but likely 
deemed acceptable since `String`s are often interned, but strictly speaking, a 
change listener that is keeping a reference to a received new value may be 
holding on to duplicate `equals()` data. So if you really care about the 
**exact** reference/instance (and not pinning a duplicate object in memory if 
you keep a reference), you must use an `InvalidationListener`.

That in turn relies on a few assumptions we're already making on property 
values:
- The value must not be mutated in place; changes must be made by replacing it 
via `set`; in other words, the value must be immutable for as long as the 
property or a listener holds it
- The value must have a stable, meaningful `equals()` implementation
- Setting a value that is `equals()` to the current one is not a change: for 
`ObjectProperty` it still invalidates (so invalidation listeners run) but 
produces no change event; for `StringProperty` it does nothing at all
- `ChangeListener` must therefore not be used to observe the exact instance; 
instead use `InvalidationListener`, since the change listener's `newValue` may 
be an earlier, `equals()` instance rather than the current one

The value properties and `String` all match those criteria, and for most object 
properties in JavaFX we use immutable values (i.e. `Color`, `Insets`, `Border`, 
etc.)

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

PR Review Comment: https://git.openjdk.org/jfx/pull/1081#discussion_r4177186959

Reply via email to