JavaFX InvalidationListener 或 ChangeListener

11

我只对属性是否发生变化感兴趣,而不关心具体的新值。

注册一个InvalidationListener是否比注册一个ChangeListener更有优势?

我之前认为改变属性会先使该属性失效并通知所有失效监听器。只有在有注册的改变监听器或有人请求此属性时,该属性才会被“验证”/重新计算,并使用新值更新所有改变监听器。

由于我对实际值不感兴趣,因此我假设仅监听失效事件(属性已更改但未被重新计算,一种中间状态)可以提高性能。


3
这个解释可能会有所帮助。 - trashgod
谢谢,我已经更新了问题。 - kerner1000
3个回答

17
您需要为此实现一个ChangeListener。只有当值变得无效时,才会执行InvalidationListener。请参见文档
根据ObservableValue的Java文档:
ObservableValue生成两种类型的事件:更改事件和无效事件。更改事件表示值已更改。如果当前值不再有效,则会生成无效事件。如果ObservableValue支持延迟评估,则此区别变得重要,因为对于延迟计算的值,人们不知道无效值是否确实已更改,直到重新计算为止。因此,生成更改事件需要急切的评估,而可以为急切和懒惰的实现生成无效事件。
我添加了一个简单的示例。
public static void main(String[] args) {

    SimpleIntegerProperty one = new SimpleIntegerProperty(1);
    SimpleIntegerProperty two = new SimpleIntegerProperty(0);

    // the binding we are interested in
    NumberBinding sum = one.add(two);
    sum.addListener(observable -> System.out.println("invalidated"));

    // if you add a value change listener, the value will NOT be evaluated lazy anymore
    //sum.addListener((observable, oldValue, newValue) -> System.out.println("value changed from " + oldValue + " to " + newValue));

    // is valid, since nothing changed so far
    System.out.println("sum valid: " + sum.isValid());
    // will invalidate the sum binding
    two.set(1);
    one.set(2); // invalidation event NOT fired here!
    System.out.println("sum valid: " + sum.isValid());
    // will validate the sum binding, since it is calculated lazy when getting the value
    System.out.println("sum: " + sum.getValue());
    System.out.println("sum valid: " + sum.isValid());
}

使用 InvalidationListener 的问题在于,如果值再次变为无效状态,您将不会收到更改通知,因为它已经是无效的。您需要使用更改监听器来实现这一点。
在属性上注册更改监听器将禁用延迟评估,因此每次更改监听器被触发时都会触发无效事件。
尝试在我添加的示例中进行操作。

但是当值发生变化时,它不总是首先变得无效吗? - kerner1000
4
是的,它会生效一次,直到再次验证(通过调用属性的getter方法)。因此,如果您多次设置值,则只有在第一次更改该值时才会收到通知,进一步更改将不会触发事件。 - Ortwin Angermeier

6
来自书籍的内容:

仅当其内容状态从有效更改为无效时,可观察对象才应生成一次失效事件。也就是说,连续多个失效应该只生成一个失效事件。

以下是一个小例子以说明这一点。
public class stackOverflowListenerQuestion extends Application {

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


    @Override
    public void start( Stage primaryStage ) throws Exception {
        IntegerProperty money  = new SimpleIntegerProperty(1);
        money.addListener(observable -> System.out.println("we should notify the listener"));
        money.set(10);
        money.set(20);
        money.set(30);
        System.out.println(money.getValue());


        IntegerProperty moreMoney  = new SimpleIntegerProperty(1);
        moreMoney.addListener(( observable, oldValue, newValue ) -> System.out.println("we should notify the listener very quickly"));
        moreMoney.set(100);
        moreMoney.set(200);
        moreMoney.set(300);
        System.out.println(moreMoney.getValue());
        Platform.exit();
    }
}

输出

we should notify the listener
30
we should notify the listener very quickly
we should notify the listener very quickly
we should notify the listener very quickly
300

money 属性关联的侦听器是 Invalidationlistener 类型的,从输出中我们可以看到 InvalidationListenerChangeListener 之间在事件方面的差异。

再举一个详细的例子:

 public class InvalidationListener extends Application {

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

        @Override
        public void start( Stage primaryStage ) throws Exception {
            Person p1 = new Person();
            Person p2 = new Person();
            Person p3 = new Person();

            NumberBinding total = Bindings.add(p1.moneyProperty().add(p2.moneyProperty()), p3.moneyProperty());
            //to see the differences between InvalidationListener and ChangeListener, yous should test them separately and watch the printed result to understand.

          //  total.addListener(( observable, oldValue, newValue ) -> System.out.println("change event occurred, we should notify the listeners"));
            total.addListener(observable -> System.out.println("Invalidation occurred, we should notify the listeners but lazily"));

            p1.setMoney(100);
            System.out.println("total.isValid() = " + total.isValid());
            p2.setMoney(200);
            System.out.println("total.isValid() = " + total.isValid());
            p3.setMoney(200);
            System.out.println("total.isValid() = " + total.isValid());
            System.out.println("total = " + total.getValue());
            System.out.println("total.isValid() = " + total.isValid());
            p3.setMoney(150);
            System.out.println("total.isValid() = " + total.isValid());
            System.out.println("total = " + total.getValue());
            System.out.println("total.isValid() = " + total.isValid());
            Platform.exit();//shutdown the JavaFx Application Thread
        }

        static class Person{
            private IntegerProperty money = new SimpleIntegerProperty();

            public final int getMoney() {
                return money.get();
            }

            public final void setMoney( int money ) {
                this.money.set(money);
            }

            public IntegerProperty moneyProperty() {
                return money;
            }
        }
    }

当您使用ChangeListener时,每当更改发生时,事件都会被触发。但是,当您使用InvalidationListener时,情况并非如此。
同一本书中的内容:
引用: 属性在其值状态从有效更改为无效时首次生成失效事件。 JavaFx中的属性使用惰性计算。当无效属性再次变为无效时,不会生成失效事件。当重新计算无效属性时(例如通过调用其get()或getValue()方法),无效属性变为有效。

3

如果您计划使用InvalidationListener,请记住以下两个规则:

  1. 确保即使没有更改发生,也不会出现问题调用您的监听器。
  2. 如果您希望监听器在每次更改时都被触发(通常这是监听器的目的),请确保监听器调用了已注册的可观察对象的getter方法。

否则,请切换到ChangeListener。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接