JavaのPropertyChangeListenerでプロパティが変更されたことを検知する

## 1. はじめに

今回はJavaの標準APIである`PropertyChangeListener`を利用して、プロパティが変更されたことを検知する方法について説明したいと思います。

JavaのGUIアプリであるSWTやSwing,AWTではイベントを検知する処理はよく知られていますが、普通のJavaクラスでもプロパティが変更されたイベントを検知する仕組みが用意されています。

## 2. 実装

### 2.1. チェック対象のクラス

```java:Item.java
package com.example.demo;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class Item {

    private int price;

    // ★ポイント１
    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);

    // ★ポイント２
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        this.pcs.addPropertyChangeListener(listener);
    }

    // ★ポイント３
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        this.pcs.removePropertyChangeListener(listener);
    }

    public int getPrice() {
        return price;
    }

    // ★ポイント４
    public void setPrice(int price) {
        int oldPrice = this.price;
        this.price = price;
        this.pcs.firePropertyChange("price", oldPrice, price);
    }

    // generated by eclipse
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Item [price=");
        builder.append(price);
        builder.append("]");
        return builder.toString();
    }
}
```

**★ポイント１**
`java.beans.PropertyChangeSupport`のインスタンスを生成し、フィールドとして定義します。
`PropertyChangeSupport`のコンストラクタ引数はイベントの発生元（検知対象）のオブジェクトのため`this`を指定します。

**★ポイント２**
`java.beans.PropertyChangeListener`を★ポイント１で定義した`PropertyChangeSupport`に追加するメソッドを実装します。

**★ポイント３**
`PropertyChangeSupport`から`java.beans.PropertyChangeListener`を削除するメソッドを実装します。
★ポイント２、★ポイント３はセットで実装するお決まりのものだと思ってください。

**★ポイント４**
`PropertyChangeSupport`の`firePropertyChange`メソッドでプロパティの変更イベントを発生させます。

* 第１引数　：　変更されたプロパティ名。文字列で指定するので間違えないように！
* 第２引数　：　変更前のプロパティの値
* 第３引数　：　変更後のプロパティの値

```java:Person.java
package com.example.demo;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class Person {

    private String name;

    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        this.pcs.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        this.pcs.removePropertyChangeListener(listener);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        String oldName = this.name;
        this.name = name;
        this.pcs.firePropertyChange("name", oldName, name);
    }

    // generated by eclipse
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Person [name=");
        builder.append(name);
        builder.append("]");
        return builder.toString();
    }
}
```

### 2.2. 変更されたことを検知するクラス

```java:DemoListener.java
package com.example.demo;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

// ★ポイント５
public class DemoListener implements PropertyChangeListener {

    // ★ポイント６
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        // ★ポイント７
        Object eventTriggerObject = evt.getSource();
        String propertyName = evt.getPropertyName();

        // ★ポイント８
        if (Item.class.isInstance(eventTriggerObject)) {
            if ("price".equals(propertyName)) {
                // ★ポイント９
                Integer oldValue = (Integer) evt.getOldValue();
                Integer newValue = (Integer) evt.getNewValue();
                System.out.println("[Item.price] oldValue:" + oldValue + " -> newValue:" + newValue);
            }
        } else if (Person.class.isInstance(eventTriggerObject)) {
            if ("name".equals(propertyName)) {
                String oldValue = (String) evt.getOldValue();
                String newValue = (String) evt.getNewValue();
                System.out.println("[Person.name] oldValue:" + oldValue + " -> newValue:" + newValue);
            }
        }
    }
}
```

**★ポイント５**
`java.beans.PropertyChangeListener`インターフェースを実装したクラスを定義します。

**★ポイント６**
`propertyChange`メソッドをオーバーライドしてプロパティの変更イベントをハンドリングする処理を実装します。

**★ポイント７**
`java.beans.PropertyChangeEvent`の`getSource()`メソッドで変更イベントの発生元のオブジェクトを取得します。
また、変更されたプロパティは`getPropertyName()`メソッドでプロパティ名が取得できるので、これを利用して特定します。

**★ポイント８**
今回は`Item`クラスと`Person`クラスの２つにリスナを登録しているため、どちらのインスタンスが発生元か判断する必要があります。

**★ポイント９**
プロパティの変更前の値は`getOldValue()`メソッド、変更後の値は`getNewValue()`メソッドでそれぞれ取得します。
戻り値は`Object`なので、プロパティのデータ型に応じてキャストする必要があります。

### 2.3. 動作確認のデモアプリのクラス

```java:App.java
package com.example.demo;

public class App {

    public static void main(String[] args) {
        Item item = new Item();
        System.out.println(item);
        item.setPrice(500);
        System.out.println(item);

        DemoListener demoListener = new DemoListener();
        item.addPropertyChangeListener(demoListener);

        item.setPrice(700);
        item.setPrice(1000);

        Person person = new Person();
        System.out.println(person);
        person.setName("Taro");
        System.out.println(person);

        person.addPropertyChangeListener(demoListener);
        person.setName("5zm");
        person.setName("Yamada");

        item.removePropertyChangeListener(demoListener);
        person.removePropertyChangeListener(demoListener);
    }
}
```

## 3. 実行結果

```console
Item [price=0]
Item [price=500]
[Item.price] oldValue:500 -> newValue:700
[Item.price] oldValue:700 -> newValue:1000
Person [name=null]
Person [name=Taro]
[Person.name] oldValue:Taro -> newValue:5zm
[Person.name] oldValue:5zm -> newValue:Yamada
```

`addPropertyChangeListener`でリスナを追加する前は`setter`で値を設定しても何も起こらず、リスナを追加した後は変更した内容がリスナによって検知されていることが分かるかと思います。

## 4. さいごに

今回はJavaの標準APIである`PropertyChangeListener`を利用して、プロパティが変更されたことを検知する方法について説明しました。
`PropertyChangeListener`を利用することで、特別なライブラリ等を準備することなく、手軽にイベント処理を試せるかと思います。