Binding and listener behavior in JavaFX is sometimes surprising to users without this being adequately explained in the documentation.
For example:
x.bind(y)
vs
y.addListener((ov, old, current) -> x.set(current));
The above two examples function nearly identical. However, if `y` is replaced with `y.concat("!")` the symmetry breaks.
The version using `bind` keeps working as expected, however the version using the (strong) listener stops working after a GC. The reason for this is the weak binding between `y` and `y.concat`. As `y.concat` is not strongly referenced anywhere (`y` is only weakly referencing it), `y.concat` gets GC'd.
When looking at the documentation, neither `addListener` nor `concat` makes mention of this behavior. `concat` (and many similar functions) need to be documented at a minimum that they're using a weak listener IMHO.
In short, when using a listener, each step of a mapped chain (y.concat("!").concat("?")) must be strongly referenced in order for it to not be accidently GC'd, while for `bind` this is not the case -- the reason is that `bind`, which only allows a single binding, can strongly refer to its only source.
There is another source of confusion:
When an `ObservableValue` is exposed as a returned value or is passed as a parameter, users need to guess if this value is strongly referenced or not if they intend to add a listener to it:
public ObservableValue<String> textProperty() {
return textProperty;
}
public ObservableValue<String> quotedTextProperty() {
return startQuote.concat(textProperty).concat("\"");
}
Calling one of these methods and adding a listener to its result would behave differently depending on which method is called.
Note that with Fluent bindings this is not the case:
public ObservableValue<String> textProperty() {
return textProperty;
}
public ObservableValue<String> quotedTextProperty() {
return textProperty.map(text -> "\"" + text + "\"");
}
Both would behave the same when a listener is added.
This ticket was created in response to the discussion at: https://github.com/openjdk/jfx/pull/675#issuecomment-1176975103
For example:
x.bind(y)
vs
y.addListener((ov, old, current) -> x.set(current));
The above two examples function nearly identical. However, if `y` is replaced with `y.concat("!")` the symmetry breaks.
The version using `bind` keeps working as expected, however the version using the (strong) listener stops working after a GC. The reason for this is the weak binding between `y` and `y.concat`. As `y.concat` is not strongly referenced anywhere (`y` is only weakly referencing it), `y.concat` gets GC'd.
When looking at the documentation, neither `addListener` nor `concat` makes mention of this behavior. `concat` (and many similar functions) need to be documented at a minimum that they're using a weak listener IMHO.
In short, when using a listener, each step of a mapped chain (y.concat("!").concat("?")) must be strongly referenced in order for it to not be accidently GC'd, while for `bind` this is not the case -- the reason is that `bind`, which only allows a single binding, can strongly refer to its only source.
There is another source of confusion:
When an `ObservableValue` is exposed as a returned value or is passed as a parameter, users need to guess if this value is strongly referenced or not if they intend to add a listener to it:
public ObservableValue<String> textProperty() {
return textProperty;
}
public ObservableValue<String> quotedTextProperty() {
return startQuote.concat(textProperty).concat("\"");
}
Calling one of these methods and adding a listener to its result would behave differently depending on which method is called.
Note that with Fluent bindings this is not the case:
public ObservableValue<String> textProperty() {
return textProperty;
}
public ObservableValue<String> quotedTextProperty() {
return textProperty.map(text -> "\"" + text + "\"");
}
Both would behave the same when a listener is added.
This ticket was created in response to the discussion at: https://github.com/openjdk/jfx/pull/675#issuecomment-1176975103
- relates to
-
JDK-8274771 Map, FlatMap and OrElse fluent bindings for ObservableValue
- Resolved