具有提取器的 JavaFX ObservableList 根据侦听器的存在更改行为

Ste*_*fer 2 java javafx

以下问题是我的 JavaFX 应用程序中错误的根源。奇怪的是,该行为取决于特定的 JavaFX 属性是否附加了侦听器。当属性被 a 观察时一切正常ChangeListener,否则无效。它开始让我发疯......

我设法将其分解为一个最小的代码示例。首先,我们需要一个类来公开一个时不时变化的属性。这里就叫nameProperty(). 在此示例中,我选择生成一个单独的线程来不断修改属性,但在实际应用程序中,它是通过用户交互发生的。

class TestClass {
    private final SimpleObjectProperty<String> name = new SimpleObjectProperty<>();

    public TestClass() {
        new Thread(() -> {
            while(true) {
                try {
                    Thread.sleep(1000);
                    Platform.runLater(() -> name.set("A"));
                    Thread.sleep(1000);
                    Platform.runLater(() -> name.set("B"));
                }
                catch(InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }

    public ReadOnlyObjectProperty<String> nameProperty() {
        return name;
    }
}
Run Code Online (Sandbox Code Playgroud)

在 main 方法中,在 FX 应用程序线程上,ObservableList<TestClass>使用提取器创建 an ,以便nameProperty()由 报告对列表元素的更改ListChangeListener。然后,我们创建测试类的一个实例,将其添加到列表中,并添加 aListChangeListener以观察列表的更新。

public static void main(String[] args) throws Exception {
    Platform.startup(() -> {
        Callback<TestClass, Observable[]> extractor = obj -> new Observable[]{ obj.nameProperty() };
        ObservableList<TestClass> list = FXCollections.observableArrayList(extractor);

        TestClass test = new TestClass();
        list.add(test);

        list.addListener((ListChangeListener<TestClass>) c -> {
            while(c.next()) {
                if(c.wasUpdated()) {
                    System.out.println("List element was updated");
                }
            }
        });
    });

    Thread.sleep(60*60*1000);
}
Run Code Online (Sandbox Code Playgroud)

我对输出的期望是,由于列表提取器和属性不断修改,它看起来像这样:

List element was updated
List element was updated
List element was updated
List element was updated
List element was updated
...
Run Code Online (Sandbox Code Playgroud)

但它看起来像这样:

List element was updated
*silence*
Run Code Online (Sandbox Code Playgroud)

现在奇怪的部分是,一旦ChangeListener将 a 添加到nameProperty()代码中的任何位置,例如

test.nameProperty().addListener(((observable, oldValue, newValue) -> {}));
Run Code Online (Sandbox Code Playgroud)

它似乎按预期工作,并且列表不断生成更改通知。

仅观察一个属性不应该改变与该属性绑定的其他事物的行为,对吧?但如果这是 JavaFX 中的一个错误,那么在我看来,这将是一个非常明显且根本的错误。所以也许我确实搞砸了一些事情,尽管程序看起来很简单。顺便说一下,我在 Windows 10 上使用 OpenJFX 版本 21。

Sla*_*law 5

请注意,提取器返回一个数组Observable,如果我们查看文档

此类的实现应努力生成尽可能少的事件,以避免在事件处理程序中浪费太多时间。当第一个失效事件发生时,该库中的实现将自身标记为无效。在重新计算其值并再次有效之前,它们不会再生成失效事件。

name属性一开始处于有效状态。您设置该属性,它就会失效,触发失效事件,最终列表更改侦听器会收到更新通知。但是您永远不会查询该name属性,这意味着它永远不会被验证。因此,尽管它被一遍又一遍地设置(设置为不同的值),但它仍然处于无效状态,并且不会触发任何更多的无效事件。如果您想继续收到失效事件的通知,您需要验证可观察量。

下面的示例显示了验证可观察值与不验证可观察值之间的区别:

import javafx.beans.Observable;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;


public class Main {

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

    static void doTestWithoutValidation() {
        StringProperty element = new SimpleStringProperty();

        ObservableList<Observable> list = FXCollections.observableArrayList(e -> new Observable[]{e});
        list.add(element);

        list.addListener((ListChangeListener.Change<? extends Observable> change) -> {
            while (change.next()) {
                if (change.wasUpdated()) {
                    System.out.println("List element updated");
                }
            }
        });

        System.out.println("Doing test WITHOUT validations...");

        element.set("Foo");
        element.set("Bar");
        element.set("Baz");

        System.out.println("DONE!");
        System.out.println();
    }

    static void doTestWithValidation() {
        StringProperty element = new SimpleStringProperty();

        ObservableList<Observable> list = FXCollections.observableArrayList(e -> new Observable[]{e});
        list.add(element);

        list.addListener((ListChangeListener.Change<? extends Observable> change) -> {
            while (change.next()) {
                if (change.wasUpdated()) {
                    System.out.println("List element updated");
                }
            }
        });

        System.out.println("Doing test WITH validations...");

        element.set("Foo");
        element.get(); // validate
        element.set("Bar");
        element.get(); // validate
        element.set("Baz");

        System.out.println("DONE!");
        System.out.println();
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

Doing test WITHOUT validations...
List element updated
DONE!

Doing test WITH validations...  
List element updated
List element updated
List element updated
DONE!

Run Code Online (Sandbox Code Playgroud)

您还必须小心垃圾收集。在您的代码中,在更新它以解决存在的并发问题后,我相信在(传递给)返回ObservableList后,它本身可能有资格进行垃圾收集。Runnablestartup

  • 哦,这很有趣。不知道 JavaFX 属性如此聪明。但看来你是对的。即使只是将列表的更改监听器中的操作更改为 `System.out.println("List element was Updated to " + test.nameProperty().get());` 也可以解决问题。现在对我来说剩下的问题是为什么在我的原始代码中没有使用/验证该属性,但这是另一个主题。 (2认同)
  • @Stefan如果您发现这不是您的实际代码中的问题,我建议检查是否有任何内容被过早地垃圾收集(即,如果您没有保留对所需对象的强引用)。 (2认同)