Reputation: 25
Core problem: Binding
objects invalidation listeners are not triggered if bound properties are updated outside the initialize()
method where they are declared.
Take this initialize()
method declared in a JavaFX UI controller class:
@FXML
private void initialize() {
final StringProperty stringProperty = textField.textProperty();
stringProperty.addListener(
(observable, oldValue, newValue) -> System.out.println("stringProperty value: " + newValue));
Bindings.createStringBinding(() -> "PREFIX - " + stringProperty.getValue(), stringProperty).addListener(
(observable, oldValue, newValue) -> System.out.println("StringBinding value: " + newValue));
// Editing stringProperty value inside initialize() method
stringProperty.setValue("u");
stringProperty.setValue("ua");
stringProperty.setValue("ua");
stringProperty.setValue("uaa");
}
As you can see there I declare a StringBinding
that depends on the text property of a TextField, called stringProperty
, and a ChangeListener
that requests the compute of the StringBinding
when it gets invalid.
If I edit stringProperty
value inside the initialize method both stringProperty
and StringBinding
change listeners are triggered, while if I edit the stringProperty
value from the UI only the stringBinding
change listener is triggered.
Can someone explain me why this happens?
Upvotes: 0
Views: 110
Reputation: 46255
Since no strong references to the StringBinding
created by Bindings.createStringBinding
exist it is eventually being garbage collected. Once that happens, the listener you added will be garbage collected along with it.
I don't think that this is the point because
Binding
object listen to their dependencies (Observable
objects) by anInvalidationListener
, and theObsevable.addListener(InvalidationListener)
documentation states that "The Observable stores a strong reference to the listener which will prevent the listener from being garbage collected and may result in a memory leak."
This is true, but take a look at the listener implementation the XXXBinding
classes use:
/*
* Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.javafx.binding;
import java.lang.ref.WeakReference;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakListener;
import javafx.beans.binding.Binding;
public class BindingHelperObserver implements InvalidationListener, WeakListener {
private final WeakReference<Binding<?>> ref;
public BindingHelperObserver(Binding<?> binding) {
if (binding == null) {
throw new NullPointerException("Binding has to be specified.");
}
ref = new WeakReference<Binding<?>>(binding);
}
@Override
public void invalidated(Observable observable) {
final Binding<?> binding = ref.get();
if (binding == null) {
observable.removeListener(this);
} else {
binding.invalidate();
}
}
@Override
public boolean wasGarbageCollected() {
return ref.get() == null;
}
}
As you can see, the listener instance that's added to the dependencies (i.e. the Observable
s) is a WeakListener
and only maintains a weak reference to the Binding
. This allows the Binding
to be garbage collected even if it hasn't been properly disposed. This is done to help prevent a memory leak in the case the Binding
has fallen out of scope but the Observable
s haven't.
In other words, the Observable
maintains a strong reference to the InvalidationListener
, but the InvalidationListener
maintains a weak reference to the Binding
.
This sort of behavior is documented in "remote" places, including Property#bind(ObservableValue)
:
Create a unidirection binding for this
Property
.Note that JavaFX has all the bind calls implemented through weak listeners. This means the bound property can be garbage collected and stopped from being updated.
And Binding#dispose()
:
Signals to the
Binding
that it will not be used anymore and any references can be removed. A call of this method usually results in the binding stopping to observe its dependencies by unregistering its listener(s). The implementation is optional.All bindings in our implementation use instances of
WeakInvalidationListener
, which means usually a binding does not need to be disposed. But if you plan to use your application in environments that do not supportWeakReferences
you have to dispose unusedBinding
s to avoid memory leaks.
Note: The implementation doesn't appear to use WeakInvalidationListener
, but the effect is the same.
Upvotes: 0