Java 同一クラスのオブジェクトのプロパティ同士の差分だけ取り出す汎用メソッド


### 導入
String.equals("A","B")みたいな感じで，オブジェクト同士の比較はそれぞれあるけど，
`オブジェクトの中のプロパティ同士の比較と，その相違がある部分の抽出ってどうやるの？`
と思って調べました．

やりたいこととしては，以下のような感じのオブジェクトがあったときに，
（オブジェクトの中身をjsonで説明するのがわかりやすいと思ったので，jsonで表現しました.使用言語はあくまでもJavaです）

~~~json
//object1
{
  "id":1,
  "name":"オリジナルです",
  "description": "同一です",
}

//object2
{
  "id":2,
  "name":"クローンです",
  "description": "同一です",
}
~~~

このパターンの`Object1`と`Object2`のプロパティ同士を比較して，
相違があるプロパティのみ出力してくれる，以下のようなやつが欲しいです．

~~~java
//以下のような例で比較できて，
CompareHelper.diff(Object1,Object2);

//出力はこんな感じにしてくれるやつ 
{
  "id":2,
  "name":"クローンです",
}
~~~






### 参考サイト
さっそく自分で調べてみて，やりたいことが載っていたのがこちらですが，
https://ocs.hatenadiary.org/entry/20101204/1291396815

`ObjectUtils.equal(aPropValue, bPropValue)`
が非推奨になっていたので，
`ObjectUtils.notEqual(aPropValue, bPropValue)`
に変更してみたのと，使用例を参考にしてもらえればと思います．


- SpringFramework
- lombok
- commons.lang3

は参考サイトの通りで，ライブラリを使用しています．

 


~~~java
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import lombok.Data;

public class CompareHelper {

    public static <T> List<Difference> diff(T a, T b) {
        BeanWrapper aWrap = new BeanWrapperImpl(a);
        PropertyDescriptor[] pds = aWrap.getPropertyDescriptors();
        List<String> propertyNames = new ArrayList<String>();
        for (PropertyDescriptor pd : pds) {
            if (aWrap.isReadableProperty(pd.getName())) {
                propertyNames.add(pd.getName());
            }
        }

        BeanWrapper bWrap = new BeanWrapperImpl(b);
        List<Difference> differences = new ArrayList<Difference>();

        for (String propertyName : propertyNames) {
            Object aPropValue = aWrap.getPropertyValue(propertyName);
            Object bPropValue = bWrap.getPropertyValue(propertyName);
            if (ObjectUtils.notEqual(aPropValue, bPropValue)) {
                Difference d = new Difference();
                d.setPropertyName(propertyName);
                d.setLeft(aPropValue);
                d.setRight(bPropValue);
                differences.add(d);
            }
        }

        return differences;
    }

    @Data
    public static class Difference {
        private String propertyName;
        private Object left;
        private Object right;
    }
}
~~~


###使用するとき
使用するときには以下のような感じで，
みたいな感じで，同一オブジェクト同士を比較し，違いがあるプロパティ名とその値を取り出すことができます． 

Spring使いとして使用するシーンはこんな感じかなあ．．．



~~~java

//比較するオブジェクトの例として，よくあるユーザエンティティを使用してみました
@Entity
public class User implements Cloneable {

    // Object#cloneはprotectedのため、デフォルトで呼び出すことができないため，
    // cloneメソッドをオーバーライドしておく
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String description;
}


//上記のようなユーザのエンティティ同士を比較します．
public void main(){
    //オリジナルのオブジェクトは，こんな感じで取得していたとする．
    //オリジナルの name は "名前変更前です" とします．
    User original = userService.findByUserName("original");

    //オリジナルのクローンを作成
    User clone = original.clone();    

    //オリジナルの名前に変更があった
    original.setName("名前変更しました");

    //オリジナルとクローンを比較
    List<Difference> diffList = CompareHelper.diff(clone, original);
    for (Difference diff : diffList) {
        System.out.println( diff.getPropertyName() + " : " + diff.getLeft() + " -> " + 
     diff.getRight() );
    }    
}

// 出力は以下のようになります
// > name : 名前変更前です -> 名前変更しました 

~~~


### 雑感

軽く調べた場合，比較方法自体の説明
（`equals`を使えとか，`==`や`===`などのよくあるハマりどころの解説)
をするサイトが出てきてしまい，なかなかやりたいところにたどり着けなかったです．


そもそも他の言語とか，フレームワーク，ライブラリでは似たようなことできるユーティリティがあるのかしら？

今回は参考サイトを元に勉強させてもらいましたが，
ライブラリを使わずに基本言語仕様のままでプロパティ同士の差分をだすには・・・
それぞれのクラスごと自前で`diff`メソッドでも作成するしかないのかな？


知っている方，教えてください！