Feedback requested for infrastructure for properties that wish to delay registering listeners
Michael Strauß
michaelstrau2 at gmail.com
Sat Feb 11 05:31:09 UTC 2023
I think that we are conflating two distinct questions in this discussion:
1. The first question is whether a listener that is registered on an
ObservableValue should prevent the ObservableValue from being eligible
for garbage collection.
2. The second question is whether the object contained in an
ObservableValue should hold a strong reference back to the
ObservableValue.
I'm only addressing the second question here. ObservableValue is a
property system: it allows developers to declare named values (much
like fields, but in addition to fields, it also allows observers to
see when the value was changed). It seems obvious to me that, while a
field refers to its value, the value of a field should not magically
refer back to the field itself (or its declaring object). Unless, of
course, the back-reference is manually added by the developer.
Similarly, the value of an ObservableValue should not magically refer
back to the ObservableValue; it is a one-way dependency by default.
This one-way dependency makes a lot of sense: it binds the lifetime of
the contained value to the lifetime of the property wrapper. The
inverse makes no sense: why should the lifetime of the property
wrapper be bound to the lifetime of its value? Plain fields don't work
like this, neither do Beans, C#'s language properties, WPF's
DependencyProperty, or any other property system that I've come
across.
And that's how most implementations in JavaFX work. Consider
ObjectProperty: the contained value will not refer to ObjectProperty,
regardless of whether listeners are registered on ObjectProperty.
But ListProperty is an anomaly: it adds a ListChangeListener to its
contained list, which causes the contained list to magically refer
back to ListProperty without the intervention of the developer. This
violates the expectation that there's a one-way dependency between an
ObservableValue and its contained value.
The anomaly is caused by the decision to have ListProperty also
implement ObservableList: this makes it looks like ListProperty (this
is our ObservableValue) is actually the same thing as its contained
value (the ObservableList). But it's not, from the perspective of
ObservableValue these are two very distinct things. One is the
wrapper, the other is the contained value.
Purely as an implementation detail, ListProperty needs the internal
ListChangeListener to replay the changes of its contained list, in
order to perfectly masquerade as its contained list. But
implementation details shouldn't change the semantics of the
fundamental API, and this is why the internal dependency between
ListProperty and its contained list needs to be broken by a weak
reference.
Note that I'm not talking about listener semantics at all. This is
just about the semantics that govern the relationship between an
ObservableValue and its contained value. Florian's PR is a targeted
fix for this very specific bug, and I remain convinced that this is a
bug.
Furthermore, fixing this bug has no bearing on whether and how we
address question 1, which you are passionate about. Leaving the
current behavior as-is does not bring us closer to having "sticky
listeners". It seems to me that much of what you've written is an
endorsement for sticky listeners, but does not address the very narrow
problem of the PR in question.
>> But the test shouldn't ever fail, because the ObservableList that is
>> wrapped by ListProperty should never keep a strong reference to the
>> ListProperty, even if the ListProperty is itself observed.
> Why should it work that way? The alternative is that my listener is
> discarded without my knowledge.
You didn't add a listener to the contained list; you added a listener
to the property wrapper. This listener is not discarded.
It may seem like it was, but that's only because ListProperty fooled
us into thinking it was the same thing as its contained list.
More information about the openjfx-dev
mailing list