Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
412 views
in Technique[技术] by (71.8m points)

java - Conditional binding doesn't work as expected

Take a look at Layout -> initPanelSizeBinding(). The problem is then() is being executed even though panelProperty.get() is null. Am I doing something wrong?

You can try it out yourself, below is the full reproducible example. (Tested on OpenJFX 15)

Reproducible example - click here (pastebin)

or take a look below:

Code:

Main.java

public class Main {
 
    public static void main(String[] args) {
        Layout layout = new Layout();
        /*
        WARNING: Exception while evaluating binding
        java.lang.NullPointerException: Cannot invoke "Panel.getWidth()" because the return value of "javafx.beans.property.ObjectProperty.get()" is null
            at Layout.lambda$initPanelSizeBinding$0(Layout.java:20)
            at javafx.beans.binding.Bindings$6.computeValue(Bindings.java:358)
            at javafx.beans.binding.ObjectBinding.get(ObjectBinding.java:157)
            at javafx.beans.binding.ObjectExpression.getValue(ObjectExpression.java:49)
            at com.sun.javafx.binding.ExpressionHelper.addListener(ExpressionHelper.java:53)
            at javafx.beans.binding.ObjectBinding.addListener(ObjectBinding.java:77)
            at javafx.beans.binding.When$ObjectCondition.<init>(When.java:757)
            at javafx.beans.binding.When$ObjectConditionBuilder.otherwise(When.java:854)
            at Layout.initPanelSizeBinding(Layout.java:24)
            at Layout.<init>(Layout.java:12)
            at Main.main(Main.java:4)
         */
    }
 
}

Layout.java

public final class Layout {
 
    public Layout() {
        panelSize.bind(initPanelSizeBinding(panel));
        // there's no need to do anything with panel, because it's an example
    }
 
    private ObjectBinding<Dimension2D> initPanelSizeBinding(ObjectProperty<Panel> panelProperty) {
        return Bindings
                .when(panelProperty.isNotNull())
                .then(Bindings.createObjectBinding(
                        () -> new Dimension2D(panelProperty.get().getWidth(), panelProperty.get().getHeight()),
                        Bindings.selectDouble(panelProperty, "width"),
                        Bindings.selectDouble(panelProperty, "height"))
                )
                .otherwise(new Dimension2D(150.0, 150.0));
    }
 
    //------Properties
 
    //panel
 
    private final ObjectProperty<Panel> panel = new SimpleObjectProperty<>
            (Layout.this, "panel", null);
 
    public ObjectProperty<Panel> panelProperty() {
        return panel;
    }
 
    public void setPanel(Panel value) {
        panel.set(value);
    }
 
    public Panel getPanel() {
        return panel.get();
    }
 
    //panelSize
 
    private final ReadOnlyObjectWrapper<Dimension2D> panelSize = new ReadOnlyObjectWrapper<>
            (Layout.this, "panelSize");
 
    public ReadOnlyObjectProperty<Dimension2D> panelSizeProperty() {
        return panelSize.getReadOnlyProperty();
    }
 
    public Dimension2D getPanelSize() {
        return panelSize.get();
    }
 
}

Panel.java

public final class Panel {
 
    private final VBox rootNode;
 
    public Panel() {
        rootNode = new VBox();
        width.bind(rootNode.widthProperty());
        height.bind(rootNode.heightProperty());
    }
 
    public VBox getRootNode() {
        return rootNode;
    }
 
    //------Properties
 
    //width
 
    private final ReadOnlyDoubleWrapper width = new ReadOnlyDoubleWrapper
            (Panel.this, "width");
 
    public ReadOnlyDoubleProperty widthProperty() {
        return width.getReadOnlyProperty();
    }
 
    public double getWidth() {
        return width.get();
    }
 
    //height
 
    private final ReadOnlyDoubleWrapper height = new ReadOnlyDoubleWrapper
            (Panel.this, "height");
 
    public ReadOnlyDoubleProperty heightProperty() {
        return height.getReadOnlyProperty();
    }
 
    public double getHeight() {
        return height.get();
    }
 
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The then and otherwise bindings are, at least as of JavaFX 15, evaluated eagerly1. I'm not sure if they're supposed1 to be evaluated eagerly, but they are. So while you may be conditioning upon panelProperty.isNotNull() the binding you create with createObjectBinding still attempts to compute the value no matter what. In this case, it's probably better to forgo the use of Bindings.when and simply implement the logic yourself in a custom binding:

private ObjectBinding<Dimension2D> initPanelSizeBinding(ObjectProperty<Panel> panelProperty) {
  DoubleBinding width = Bindings.selectDouble(panelProperty, "width");
  DoubleBinding height = Bindings.selectDouble(panelProperty, "height");
  return Bindings.createObjectBinding(
      () -> {
        Panel panel = panelProperty.get();
        if (panel == null) {
          return new Dimension2D(150.0, 150.0);
        }
        return new Dimension2D(width.get(), height.get());
      },
      panelProperty,
      width,
      height);
}

1. Looking into it more, it appears the conditional binding does try to be lazy. Unfortunately, the act of the condition binding adding a listener to the then observable causes said observable to be validated (and thus eagerly evaluated). This is a consequence of how ExpressionHelper is implemented, which calls observable.getValue() to validate it. I don't understand why the code does this, especially considering it's an InvalidationListener being added which is supposed to be the lazy listener.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...