为什么TableView的更改侦听器为JavaFX8中的ObjectProperty <T>与TProperty列提供不同的结果?

Gre*_*bra 5 javafx tableview propertychangelistener javafx-8

一个相对的Java新手问题.

我有一个带有提取器的TableView,并ListChangeListener添加到底层的ObservableList中.

如果我StringProperty在数据模型中有一个列,那么如果我双击单元格然后按Enter键而不做任何更改,则更改侦听器不会检测到更改.非常好.

但是,如果我将列定义为ObjectProperty<String>并双击然后按Enter,则更改侦听器始终会检测到更改,即使没有进行更改也是如此.

为什么会这样?改变听众的观点ObjectProperty<String>StringProperty改变之间的区别是什么?

我已经阅读了SimpleStringProperty和StringProperty以及JavaFX SimpleObjectProperty <T>与SimpleTProperty之间的差异,并认为我理解这些差异.但是我不明白为什么改变监听器给TProperty/ SimpleTProperty和提供不同的结果ObjectProperty<T>.

如果它有帮助,这是一个MVCE我的有点荒谬的情况.我实际上是想让一个改变监听器工作BigDecimalLocalDate列,并且已经坚持了5天.如果我能理解为什么更改侦听器提供不同的结果,我可能能够使我的代码工作.

我正在使用JavaFX8(JDK1.8.0_181),NetBeans 8.2和Scene Builder 8.3.

package test17;

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.Observable;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.converter.DefaultStringConverter;

public class Test17 extends Application {

    private Parent createContent() {

        ObservableList<TestModel> olTestModel = FXCollections.observableArrayList(testmodel -> new Observable[] {
                testmodel.strProperty(),
                testmodel.strObjectProperty()
        });

        olTestModel.add(new TestModel("A", "a"));
        olTestModel.add(new TestModel("B", "b"));

        olTestModel.addListener((ListChangeListener.Change<? extends TestModel > c) -> {
            while (c.next()) {
                if (c.wasUpdated()) {
                    System.out.println("===> wasUpdated() triggered");
                }
            }
        });

        TableView<TestModel> table = new TableView<>();

        TableColumn<TestModel, String> strCol = new TableColumn<>("strCol");
        strCol.setCellValueFactory(cellData -> cellData.getValue().strProperty());
        strCol.setCellFactory(TextFieldTableCell.forTableColumn(new DefaultStringConverter()));
        strCol.setEditable(true);
        strCol.setPrefWidth(100);
        strCol.setOnEditCommit((CellEditEvent<TestModel, String> t) -> {
                ((TestModel) t.getTableView().getItems().get(
                        t.getTablePosition().getRow())
                        ).setStr(t.getNewValue());
        });

        TableColumn<TestModel, String> strObjectCol = new TableColumn<>("strObjectCol");
        strObjectCol.setCellValueFactory(cellData -> cellData.getValue().strObjectProperty());
        strObjectCol.setCellFactory(TextFieldTableCell.forTableColumn(new DefaultStringConverter()));
        strObjectCol.setEditable(true);
        strObjectCol.setPrefWidth(100);
        strObjectCol.setOnEditCommit((CellEditEvent<TestModel, String> t) -> {
            ((TestModel) t.getTableView().getItems().get(
                    t.getTablePosition().getRow())
                    ).setStrObject(t.getNewValue());
        });

        table.getColumns().addAll(strCol, strObjectCol);
        table.setItems(olTestModel);
        table.getSelectionModel().setCellSelectionEnabled(true);
        table.setEditable(true);

        BorderPane content = new BorderPane(table);
        return content;
    }

    public class TestModel {

        private StringProperty str;
        private ObjectProperty<String> strObject;

        public TestModel(
            String str,
            String strObject
        ) {
            this.str = new SimpleStringProperty(str);
            this.strObject = new SimpleObjectProperty(strObject);
        }

        public String getStr() {
            return this.str.get();
        }

        public void setStr(String str) {
            this.str.set(str);
        }

        public StringProperty strProperty() {
            return this.str;
        }

        public String getStrObject() {
            return this.strObject.get();
        }

        public void setStrObject(String strObject) {
            this.strObject.set(strObject);
        }

        public ObjectProperty<String> strObjectProperty() {
            return this.strObject;
        }

    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent()));
        stage.setTitle("Test");
        stage.setWidth(350);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

}
Run Code Online (Sandbox Code Playgroud)

Sla*_*law 4

StringPropertyBase通过查看和ObjectPropertyBase\xe2\x80\x94的源代码可以看出区别,特别是它们的set方法。

\n\n

StringPropertyBase

\n\n
@Override\npublic void set(String newValue) {\n    if (isBound()) {\n        throw new java.lang.RuntimeException((getBean() != null && getName() != null ?\n                getBean().getClass().getSimpleName() + "." + getName() + " : ": "") + "A bound value cannot be set.");\n    }\n    if ((value == null)? newValue != null : !value.equals(newValue)) {\n        value = newValue;\n        markInvalid();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

ObjectPropertyBase

\n\n
@Override\npublic void set(T newValue) {\n    if (isBound()) {\n        throw new java.lang.RuntimeException((getBean() != null && getName() != null ?\n                getBean().getClass().getSimpleName() + "." + getName() + " : ": "") + "A bound value cannot be set.");\n    }\n    if (value != newValue) {\n        value = newValue;\n        markInvalid();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

注意到他们检查新值是否等于旧值的方式有何不同吗?该类StringPropertyBase通过 using 进行检查Object.equals,而该类则ObjectPropertyBase使用引用相等性 ( ==/ !=)。

\n\n

我无法确切回答为什么存在这种差异,但我可以大胆猜测:AnObjectProperty可以容纳任何东西,因此有可能Object.equals很昂贵;例如当使用List或 时Set。当编码时StringPropertyBase,我猜他们认为潜力不存在,String平等的语义更重要,或者两者兼而有之。他们这样做可能有更多/更好的原因,但由于我没有参与开发,所以我不知道它们。

\n\n
\n\n

有趣的是,如果您查看它们如何处理侦听器\n( com.sun.javafx.binding.ExpressionHelper),您会发现它们使用 来检查相等性Object.equals。仅当当前有ChangeListeners 注册\xe2\x80\x94 时才会发生此相等性检查,可能是为了在没有ChangeListeners 时支持延迟求值。

\n\n

如果新值和旧值相同,则不会通知equalss 。然而,这并不能阻止s收到通知。因此,您将触发更新更改,因为该机制基于s 而不是s。ChangeListenerInvalidationListenerObservableListInvalidationListenerChangeListener

\n\n

这是相关的源代码:

\n\n

ExpressionHelper$Generic.fireValueChangedEvent

\n\n
@Override\nprotected void fireValueChangedEvent() {\n    final InvalidationListener[] curInvalidationList = invalidationListeners;\n    final int curInvalidationSize = invalidationSize;\n    final ChangeListener<? super T>[] curChangeList = changeListeners;\n    final int curChangeSize = changeSize;\n\n    try {\n        locked = true;\n        for (int i = 0; i < curInvalidationSize; i++) {\n            try {\n                curInvalidationList[i].invalidated(observable);\n            } catch (Exception e) {\n                Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);\n            }\n        }\n        if (curChangeSize > 0) {\n            final T oldValue = currentValue;\n            currentValue = observable.getValue();\n            final boolean changed = (currentValue == null)? (oldValue != null) : !currentValue.equals(oldValue);\n            if (changed) {\n                for (int i = 0; i < curChangeSize; i++) {\n                    try {\n                        curChangeList[i].changed(observable, oldValue, currentValue);\n                    } catch (Exception e) {\n                        Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);\n                    }\n                }\n            }\n        }\n    } finally {\n        locked = false;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

您可以在以下代码中看到此行为:

\n\n
import javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\n\npublic class Main {\n\n  public static void main(String[] args) {\n    ObjectProperty<String> property = new SimpleObjectProperty<>("Hello, World!");\n    property.addListener(obs -> System.out.printf("Property invalidated: %s%n", property.get()));\n    property.addListener((obs, ov, nv) -> System.out.printf("Property changed: %s -> %s%n", ov, nv));\n    property.get(); // ensure valid\n\n    property.set(new String("Hello, World!")); // must not use interned String\n    property.set("Goodbye, World!");\n  }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出:

\n\n
@Override\npublic void set(String newValue) {\n    if (isBound()) {\n        throw new java.lang.RuntimeException((getBean() != null && getName() != null ?\n                getBean().getClass().getSimpleName() + "." + getName() + " : ": "") + "A bound value cannot be set.");\n    }\n    if ((value == null)? newValue != null : !value.equals(newValue)) {\n        value = newValue;\n        markInvalid();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

  • 有趣...似乎未指定身份或相等性更改是否构成 ObservableValue 值的更改(至少我找不到任何内容) - 并且实现没有指定它们是如何实现的。嗯... (2认同)