JavaFXのInvalidationListenerの意味

InvalidationListenerの意味

例えば、SimpleBooleanProperty等のプロパティ値にリスナーを登録する場合、ChangeListenerかInvalidationListenerかを選べるだが、ChangeListenerはわかるとしてもInvalidationListenerとは何だろうか?

結論から言えば、99.99%はChangeListenerを選択すればよい。InvalidationListenerを選択しなければならない場合は、そのオブジェクトの設計者から指示があるはずだし、指示があっても下手に扱うと全く意味の無いものになってしまう。

InvalidationListenerの意味は公式マニュアルに説明があり、ちゃんと「遅延評価のため」と書いてある。

  • https://docs.oracle.com/javase/jp/8/javafx/api/javafx/beans/value/ObservableValue.html

ChangeListenerの引数を見てみれば、古い値、新しい値が指定されるのだが、InvalidationListenerの場合には値は無い。InvalidationListenerでは「値は変わりましたが、まだ結果がいくつなのか計算していません」ということ。「通知された側の要求によって、新しい値を計算してあげる」ということである。

これはつまり、「頻繁に値が変わる可能性があるが、それを計算するのはコストがかかる。だから、ユーザ側が本当に必要になるまでは計算するのをサボろう」ということ。これが遅延評価と言っている意味である。

これをChangeListenerと比べれば明らかである。ChangeListenerの方は、「値が変わりました。新しい値はこれです」と通知してくるのだから。

もちろん、InvalidationListenerを使う場合には、「リスナー全員がInvalidationListenerでなければならない」。なぜなら、一人でもChangeListenerがいれば、そいつのために値を計算しなければならないからだ。

あるいは、InvalidationListenerを使ったとしても、いきなり値を取得するなどしてしまえば、全く意味はない。その時点で値が計算されなからばならなくなるからだ。

したがって、本当にInvalidationListenerが必要な状況の場合には、対象となるオブジェクトの設計者側から、「これはコストがかかるから十分気をつけて、InvalidationListenerだけでお願い」という注意書きがあるべきであろう。「どっち選んでもいいよ」みたいな状況では決して無い。

実際に動作を検証してみる。

import javafx.beans.property.*;

public class InvalidationTest {

  void test1() {
    System.out.println("\n*** ChangeListener only");
    SimpleBooleanProperty a = new SimpleBooleanProperty();
    a.addListener((ob, o, n)->System.out.println(" ... changed called"));
    System.out.println("changing to true"); a.set(true); 
    System.out.println("changing to true"); a.set(true);
    System.out.println("changing to false"); a.set(false);
    System.out.println("changing to true"); a.set(true);
    System.out.println("changing to false"); a.set(false);
  }

  void test2() {
    System.out.println("\n*** InvalidationListener only");
    SimpleBooleanProperty a = new SimpleBooleanProperty();
    a.addListener(o->System.out.println(" ... invalidated called"));
    System.out.println("changing to true"); a.set(true); 
    System.out.println("changing to true"); a.set(true);
    System.out.println("changing to false"); a.set(false);
    System.out.println("changing to true"); a.set(true);
    System.out.println("changing to false"); a.set(false);
  }

  void test3() {
    System.out.println("\n*** Both listeners");
    SimpleBooleanProperty a = new SimpleBooleanProperty();
    a.addListener((ob, o, n)->System.out.println(" ... changed called"));
    a.addListener(o->System.out.println(" ... invalidated"));
    System.out.println("changing to true"); a.set(true); 
    System.out.println("changing to true"); a.set(true);
    System.out.println("changing to false"); a.set(false);
    System.out.println("changing to true"); a.set(true);
    System.out.println("changing to false"); a.set(false);
  }

  void test4() {
    System.out.println("\n*** Invalidation with get");
    SimpleBooleanProperty a = new SimpleBooleanProperty();
    a.addListener(o->System.out.println(" ... invalidated called "  + o.toString()));
    System.out.println("changing to true"); a.set(true); 
    System.out.println("changing to true"); a.set(true);
    System.out.println("changing to false"); a.set(false);
    System.out.println("changing to true"); a.set(true);
    System.out.println("changing to false"); a.set(false);
  }


  public static void main(String[]args) {
    InvalidationTest test = new InvalidationTest();
    test.test1();
    test.test2();
    test.test3();
    test.test4();
  }

}

結果は以下。


*** ChangeListener only // 値が変更されるたびに呼び出される。 changing to true ... changed called changing to true changing to false ... changed called changing to true ... changed called changing to false ... changed called *** InvalidationListener only // 最初の通知以降は値計算がされていないので、値変更の事実があっても、もはや通知されない。 changing to true ... invalidated called changing to true changing to false changing to true changing to false *** Both listeners // 二つのリスナーを混ぜてつかうと、全く意味がなくなる。 changing to true ... invalidated ... changed called changing to true changing to false ... invalidated ... changed called changing to true ... invalidated ... changed called changing to false ... invalidated ... changed called *** Invalidation with get // InvalidationListenerだけの場合でも、そのつど値を取得してしまうと意味がなくなる。 changing to true ... invalidated called BooleanProperty [value: true] changing to true changing to false ... invalidated called BooleanProperty [value: false] changing to true ... invalidated called BooleanProperty [value: true] changing to false ... invalidated called BooleanProperty [value: false]