When using a lambda expression to add a listener to some JavaFX property, we might fall into a classic trap where we will be facing memory leaks or where the listener we set won't be invoked:
void init() {
someProperty.addListener(this::onChanged); // might create a memory leak
someProperty.addListener(new WeakChangeListener<String>(this::onChanged)); // no memory leak, but the listener won't be called
}
The following solution works:
private final ChangeListener<String> changeListener = this::onChanged;
void init() {
someProperty.addListener(new WeakChangeListener<>(changeListener)); // OK, but requires an extra field + WeakChangeListener
}
Is there any other solution to set a listener with a lambda without much boilerplate code?
Edit: An other confusing situation exists, while not related to Lambda expressions but still related to the usage of weak references with JavaFX, where the following code won't work:
private final ObservableList<Person> persons = FXCollections.observableArrayList();
public ObservableList<Person> getPersons() {
return FXCollections.unmodifiableObservableList(persons); // a client invoking getPersons().addListener(someListener) won't be notified
}
But this code will work (with some additional boilerplate code though):
private final ObservableList<Person> persons = FXCollections.observableArrayList();
private final ObservableList<Person> immutableList = FXCollections.unmodifiableObservableList(persons); // needed for our clients to be notified
@SuppressWarnings("ReturnOfCollectionOrArrayField") // needed to avoid a warning
public ObservableList<Person> getPersons() {
return this.immutableList;
}
I'm using the word "confusing" here because:
- The working solution is not the one we come first; it's not "natural".
- When we face the problem for the very first time, it takes hours before we understand where the problem comes from.