Supposedly lazy binding gets evaluated right away

John Hendrikx hjohn at xs4all.nl
Tue Aug 1 20:56:05 UTC 2017


My understanding of Bindings has always been that they're supposedly 
only evaluated when actually used (ie. get() is called on them).

Javadoc for Binding claims (emphasis mine):

   "All bindings in the JavaFX runtime are calculated **lazily**. That 
means, if a dependency changes, the result of a binding is not 
immediately recalculated, but it is marked as invalid. Next time the 
value of an invalid binding is requested, it is recalculated."

So, when I create a binding, I donot expect it to be evaluated.  This 
holds true in certain cases.  However, when I then create a binding that 
refers another binding, it *DOES* get evaluated at creation time. 
Please help me understand why this is happening...

The attached code shows an example of this happening (a WARNING is 
logged, and with a different log configuration you can see an exception 
logged as well, see at end):

    Exception while evaluating select-binding [muted]
    Property 'muted' in  ObjectProperty [value: null] is null

The problem seems to be this line of code in 
com.sun.javafx.binding.ExpressionHelper#addListener which calls getValue 
but discards the result (a red flag IMHO):

    observable.getValue(); // validate observable

This is breaking the lazy binding and triggers a chain of "get" calls
without the binding being used -- and for what?  To discard the result...

The reason why my property is still "null" is because in a complicated 
framework you may still be in the process of setting up bindings.  In my 
case the "playerProperty" is actually set up after the creation of 
another class that creates bindings based on this.  However that class 
gets this WARNING log + stacktrace while only doing binding setups.

Note the property is never null when it is actually used (ie, displayed 
somewhere).  If the binding was really lazy, then no null should ever be 
encountered.

--John

The code:

   public static void main(String[] args) {
     ObjectProperty<Player> playerProperty = new SimpleObjectProperty<>();

     BooleanBinding mutedProperty = 
Bindings.selectBoolean(playerProperty, "muted");

     new StringBinding() {
       {
         bind(mutedProperty);
       }

       @Override
       protected String computeValue() {
         return "irrelevant";
       }
     };

     // optional, bind playerProperty to something that isn't null
   }


   public static class Player {
     private final BooleanProperty mutedProperty = new 
SimpleBooleanProperty();

     public BooleanProperty mutedProperty() {
       return mutedProperty;
     }
   }

}

The stacktrace:

java.lang.NullPointerException
	at 
com.sun.javafx.binding.SelectBinding$SelectBindingHelper.getObservableValue(SelectBinding.java:481)
	at 
com.sun.javafx.binding.SelectBinding$AsBoolean.computeValue(SelectBinding.java:139)
	at javafx.beans.binding.BooleanBinding.get(BooleanBinding.java:157)
	at 
javafx.beans.binding.BooleanExpression.getValue(BooleanExpression.java:56)
	at javafx.beans.binding.BooleanBinding.getValue(BooleanBinding.java:60)
	at 
com.sun.javafx.binding.ExpressionHelper.addListener(ExpressionHelper.java:54)
	at javafx.beans.binding.BooleanBinding.addListener(BooleanBinding.java:76)
	at javafx.beans.binding.StringBinding.bind(StringBinding.java:102)
	at 
hs.mediasystem.ext.media.newstyle.BindingBug$1.<init>(BindingBug.java:37)
	at hs.mediasystem.ext.media.newstyle.BindingBug.main(BindingBug.java:35)



More information about the openjfx-dev mailing list