JavaでもReactivePropertyしたいだけの人生だった

# 概要
　少し前に、「JavaFXで十分Data Bindingできるじゃん！」といった記事を書きました。
　　[Java+JavaFX+MVVMでストップウォッチアプリを作成してみた所感](https://qiita.com/YSRKEN/items/d926ac60a25e3168db7a)
　ただ、**これじゃ足りない！足りないんだよ！**　具体的には**ジェネリクスで書きたいんだよ！**
　`StringProperty`だの`BooleanProperty`だの、**お前それ実装してて一ミリもクソだと思わなかったのか**とJava開発者(SunかOracle)を小一時間問い詰めたい命名がされているクラスを眺めていて思いました。
　「じゃあジェネリクスの練習がてらReactiveProperty作ろう」と。

※最終的なコードは文末に貼ってあります

# ベースを何にするか
　とりあえず、`Property<T>`型のインスタンスを内に持ちながら、インターフェース`Property<T>`を継承することにしました。実体は……`Property<T>`を継承している`SimpleObjectProperty<T>`でいいでしょう、多分。

```sample1.java
public class ReactiveProperty<T> implements Property<T> {
    private Property<T> value;
    // コンストラクタ
    public ReactiveProperty(){
        value = new SimpleObjectProperty<>();
    }
    public ReactiveProperty(T t){
        value = new SimpleObjectProperty<>(t);
    }
}
```

　`Property<T>`を継承したので当然各種メソッドを実装する必要がありますが、全部内部のインスタンスに丸投げしました(冗長なのでコードは略)。ただ、`public void setValue(Object value){this.value.setValue((T)value);}`のように、`Object`型が引数なメソッドの場合はキャストを書かなければならないのが気持ち悪いですね……。

# ReactivePropertyっぽいメソッドを実装しよう
　一応代替物を目指しているので、`Select`や`Subscribe`などの主要なメソッドは実装しておきたいものです。通知方法ですが、内部のインスタンスが`addListener`メソッドを持っているので、そちらに追記することにしました。これだけで、ちゃんと通知機能が働いてくれるのは驚きですね。
　ただ、Javaには「拡張メソッド」の概念が存在しませんので、`Select`した結果がいきなり`ReactiveProperty<T>`になりますし、`ToReactiveProperty`は`StringPropertyなどをReactiveProperty<T>`に置き換えるためだけのメソッドになりました。

```sample2.java
public class ReactiveProperty<T> implements Property<T> {
    // ReactivePropertyからReactivePropertyを作成
    public <R> ReactiveProperty<R> select(Function<T, R> func){
        ReactiveProperty<R> retVal = new ReactiveProperty<>(func.apply(this.getValue()));
        this.addListener((sb, oldValue, newValue)->{retVal.setValue(func.apply((T)newValue));});
        return retVal;
    }
    // 2つのReactivePropertyからReactivePropertyを作成
    public <U, R> ReactiveProperty<R> combineLatest(ReactiveProperty<U> u, BiFunction<T, U, R> func){
        ReactiveProperty<R> retVal = new ReactiveProperty<>(func.apply(this.getValue(), u.getValue()));
        this.addListener((sb, oldValue, newValue)->{retVal.setValue(func.apply((T)newValue, u.getValue()));});
        u.addListener((sb, oldValue, newValue)->{retVal.setValue(func.apply(this.getValue(), (U)newValue));});
        return retVal;
    }
    // ◯◯PropertyからReactivePropertyを作成
    public static <R> ReactiveProperty<R> ToReactiveProperty(Property<R> r){
        ReactiveProperty<R> retVal = new ReactiveProperty<>(r.getValue());
        r.addListener((sb, oldValue, newValue)->{retVal.setValue(r.getValue());});
        return retVal;
    }
    // 変更通知機能
    public void subscribe(Consumer<T> cons){
        this.addListener((sb, oldValue, newValue) -> {cons.accept(this.getValue());});
    }
}
```

# まとめ
　上記のように実装することで、ジェネリクスによる通知付きプロパティを作成することができました。JavaFXプロジェクトに持ち込んで使用してみたところ、`Property<T>`を継承しているので、コントロールのプロパティとちゃんとData Bindingできますので、十分役に立つものと思われます。

# 全体のソースコード

　Java初心者が書いたヘタクソなコードですが、何卒ご了承ください。パッケージ名は[JavaでLINQを実装した方](https://qiita.com/kasecato/items/ec7ab8b392fa8bb0a732#%E8%AA%B2%E9%A1%8C)のパクリですごめんなさい。型名も変更する必要があったりするのでしょうか……？

```ReactiveProperty.java
package com.github.jrper;

import javafx.beans.InvalidationListener;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;

import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;

public class ReactiveProperty<T> implements Property<T> {
    private Property<T> value;
    // コンストラクタ
    public ReactiveProperty(){
        value = new SimpleObjectProperty<>();
    }
    public ReactiveProperty(T t){
        value = new SimpleObjectProperty<>(t);
    }

    // Propertyインターフェースを継承するので……
    @Override
    public void addListener(ChangeListener listener) {value.addListener(listener);}
    @Override
    public void removeListener(ChangeListener listener) {value.removeListener(listener);}
    @Override
    public void setValue(Object value){this.value.setValue((T)value);}
    @Override
    public void bind(ObservableValue observable) {value.bind(observable);}
    @Override
    public void unbind() {value.unbind();}
    @Override
    public boolean isBound() {return value.isBound();}
    @Override
    public void bindBidirectional(Property other) {value.bindBidirectional(other);}
    @Override
    public void unbindBidirectional(Property other) {value.unbindBidirectional(other);}
    @Override
    public Object getBean() {return value.getBean();}
    @Override
    public String getName() {return value.getName();}
    @Override
    public void addListener(InvalidationListener listener) {value.addListener(listener);}
    @Override
    public void removeListener(InvalidationListener listener) {value.removeListener(listener);}

    // getter
    public T getValue(){return value.getValue();}

    // ReactivePropertyからReactivePropertyを作成
    public <R> ReactiveProperty<R> select(Function<T, R> func){
        ReactiveProperty<R> retVal = new ReactiveProperty<>(func.apply(this.getValue()));
        this.addListener((sb, oldValue, newValue)->{retVal.setValue(func.apply((T)newValue));});
        return retVal;
    }
    // 2つのReactivePropertyからReactivePropertyを作成
    public <U, R> ReactiveProperty<R> combineLatest(ReactiveProperty<U> u, BiFunction<T, U, R> func){
        ReactiveProperty<R> retVal = new ReactiveProperty<>(func.apply(this.getValue(), u.getValue()));
        this.addListener((sb, oldValue, newValue)->{retVal.setValue(func.apply((T)newValue, u.getValue()));});
        u.addListener((sb, oldValue, newValue)->{retVal.setValue(func.apply(this.getValue(), (U)newValue));});
        return retVal;
    }
    // ◯◯PropertyからReactivePropertyを作成
    public static <R> ReactiveProperty<R> ToReactiveProperty(Property<R> r){
        ReactiveProperty<R> retVal = new ReactiveProperty<>(r.getValue());
        r.addListener((sb, oldValue, newValue)->{retVal.setValue(r.getValue());});
        return retVal;
    }
    // 変更通知機能
    public void subscribe(Consumer<T> cons){
        this.addListener((sb, oldValue, newValue) -> {cons.accept(this.getValue());});
    }
}
```