2つのオブジェクトを「差分」できるJavaライブラリはありますか?


86

Unixプログラムのdiffに類似しているがオブジェクト用のJavaユーティリティライブラリはありますか?同じタイプの2つのオブジェクトを比較し、それらの違いを表すデータ構造を生成できる(そして、インスタンス変数の違いを再帰的に比較できる)ものを探しています。私はテキスト差分のJava実装を探していません。私もよいないこれを行うためにリフレクションを使用する方法についてのヘルプを探しています。

私が維持しているアプリケーションには、この機能の壊れやすい実装があり、設計上の選択が不十分で、書き直す必要がありますが、既製のものを使用できればさらに良いでしょう。

これが私が探している種類のものの例です:

SomeClass a = new SomeClass();
SomeClass b = new SomeClass();

a.setProp1("A");
a.setProp2("X");

b.setProp1("B");
b.setProp2("X");

DiffDataStructure diff = OffTheShelfUtility.diff(a, b);  // magical recursive comparison happens here

比較した後、ユーティリティは「prop1」が2つのオブジェクト間で異なり、「prop2」が同じであることを教えてくれます。DiffDataStructureがツリーであるのが最も自然だと思いますが、コードが信頼できるものであれば、私は気難しいことはしません。


8
これを確認してください、code.google.com
p


13
これは、「複雑なオブジェクトグラフの同等性をテストする方法」の複製ではありません。私は図書館を探していますが、それを行う方法についてのアドバイスではありません。さらに、私はデルタが等しいかどうかではなく、デルタに興味があります。
ケイプロII

1
GraphComparatorユーティリティがあるgithub.com/jdereg/java-utilを見てください。このクラスは、オブジェクトグラフ間のデルタのリストを生成します。さらに、デルタをグラフにマージ(適用)することができます。事実上、List <Delta> = GraphComparator(rootA、rootB)です。GraphComparator.applyDelta(rootB、List <Delta>)も同様です。
John DeRegnaucourt 2016

私はあなたがリフレクションを使用したくない知っているが、それは間違いなくこのような何かを実装する最も簡単な/シンプルな方法です
RobOhRob

回答:


42

少し遅れるかもしれませんが、私はあなたと同じ状況にあり、まさにあなたのユースケースに合わせて独自のライブラリを作成することになりました。私は自分で解決策を考え出すことを余儀なくされたので、他の人の努力を惜しまないように、Githubでそれをリリースすることにしました。あなたはここでそれを見つけることができます:https//github.com/SQiShER/java-object-diff

---編集---

OPコードに基づく使用例を次に示します。

SomeClass a = new SomeClass();
SomeClass b = new SomeClass();

a.setProp1("A");
a.setProp2("X");

b.setProp1("B");
b.setProp2("X");

DiffNode diff = ObjectDifferBuilder.buildDefault().compare(a, b);

assert diff.hasChanges();
assert diff.childCount() == 1;
assert diff.getChild('prop1').getState() == DiffNode.State.CHANGED;

1
java-object-diffのユースケースを示すこの投稿を紹介してもいいですか?stackoverflow.com/questions/12104969/…この便利なライブラリをありがとう!
Matthias Wuttke 2012

私はあなたの投稿を見て、コメントとして投稿するには長すぎることが判明した回答を書いたので、ここにその「要点」があります:gist.github.com/3555555
SQiShER

2
きちんとした。私も自分の実装を書くことになりました。あなたの実装を実際に探求する機会が得られるかどうかはわかりませんが、リストをどのように処理するかについては興味があります。最終的に、すべてのコレクションの違いをマップの違いとして処理しました(リスト、セット、マップのいずれであるかに応じてさまざまな方法でキーを定義し、keySetでセット操作を使用します)。ただし、その方法はリストインデックスの変更に敏感すぎます。私は自分のアプリケーションに必要がなかったのでその問題を解決しませんでしたが、あなたがそれをどのように処理したかについて興味があります(テキスト差分のように、私は推測します)。
ケイプロII

2
あなたは良い点を持ち出します。コレクションを扱うのは本当に難しいです。特に、私のようにマージをサポートしている場合は。オブジェクトIDを使用して、アイテムが追加、変更、または削除されたかどうかを検出することで問題を解決しました(hashCode、equals、containsを介して)。それの良いところは、すべてのコレクションを同じように扱うことができるということです。悪い点は、同じオブジェクトを複数回含むArrayListsを(たとえば)適切に処理できないことです。そのためのサポートを追加したいのですが、それはかなり複雑なようで、幸いなことにまだ誰もそれを求めていません。:-)
SQiShER 2012

ダニエル、コメントありがとうございます!確かに、diffから元のオブジェクトを再構築することは困難ですが、私の場合、これはそれほど重要ではありません。元々、私は以前のバージョンのオブジェクトをデータベースに保存していました-お勧めします。問題は、大きなドキュメントのほとんどの部分が変更されておらず、重複したデータがたくさんあることでした。これが私が代替案を探し始めた理由であり、java-object-diffを見つけました。また、コレクション/マップで問題が発生し、「equals」メソッドを変更して、データベースプライマリ(オブジェクト全体ではなく)が等しいかどうかを確認しました。これは役に立ちました。
Matthias Wuttke 2012

39

http://javers.orgは、必要なことを正確に実行するライブラリです。compare(Object leftGraph、Object rightGraph)のようなメソッドがDiffオブジェクトを返します。Diffには、変更のリスト(ReferenceChange、ValueChange、PropertyChange)が含まれています。

given:
DummyUser user =  dummyUser("id").withSex(FEMALE).build();
DummyUser user2 = dummyUser("id").withSex(MALE).build();
Javers javers = JaversTestBuilder.newInstance()

when:
Diff diff = javers.compare(user, user2)

then:
diff.changes.size() == 1
ValueChange change = diff.changes[0]
change.leftValue == FEMALE
change.rightValue == MALE

グラフのサイクルを処理できます。

さらに、任意のグラフオブジェクトのスナップショットを取得できます。Javersには、スナップショット用のJSONシリアライザーとデシリアライザーがあり、データベースに簡単に保存できるように変更されています。このライブラリを使用すると、監査用のモジュールを簡単に実装できます。


1
これは機能しません。Javersインスタンスをどのように作成しますか?
Ryan Vettese 2014年

2
:Javersはすべてを説明する優れたドキュメントがあるjavers.org
パヴェルSzymczyk

2
私はそれを使おうとしましたが、Java> = 7専用です。皆さん、Java6のプロジェクトに取り組んでいる人はまだいます!!!
jesantana 2015年

2
比較する2つのオブジェクトに内部にオブジェクトのリストがあり、それらの違いも表示される場合はどうなりますか?ジャバーが比較できる階層のレベルはどれくらいですか?
Deepak 2016

1
はい、内部オブジェクトに違いがあります。現時点では、階層レベルのしきい値はありません。Javersは可能な限り深くなりますが、制限はスタックサイズです。
パヴェルSzymczyk

15

はい、java-utilライブラリには、2つのJavaオブジェクトグラフを比較するGraphComparatorクラスがあります。差異をデルタのリストとして返します。GraphComparatorを使用すると、デルタをマージ(適用)することもできます。このコードはJDKにのみ依存し、他のライブラリには依存しません。


12
素敵なライブラリのようです。あなたは寄稿者であることに言及する必要があります
Christos 2016

3

すべてのJaversライブラリはJava7のみをサポートしています。これをJava6プロジェクトで使用したいので、たまたまソースを取得して、Java6で機能するように変更しました。以下はgithubコードです。 。

https://github.com/sand3sh/javers-forJava6

ジャーリンク:https//github.com/sand3sh/javers-forJava6/blob/master/build/javers-forjava6.jar

Java7でサポートされている「<>」固有のキャスト変換をJava6サポートに変更しただけです。すべてのカスタムオブジェクトの比較で機能する不要なコードをいくつかコメントしたため、すべての機能が機能することを保証しません。



2

Apacheのソリューションを確認することもできます。commons-langの一部であるため、ほとんどのプロジェクトはすでにクラスパスにそれを持っています。

特定のフィールドの違いを確認してください:http
//commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/builder/DiffBuilder.html

リフレクションを使用して違いを確認します:http
//commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/builder/ReflectionDiffBuilder.html


1

このコードを使用する場所に応じて、これが役立つか、問題がある可能性があります。このコードをテストしました。

    /**
 * @param firstInstance
 * @param secondInstance
 */
protected static void findMatchingValues(SomeClass firstInstance,
        SomeClass secondInstance) {
    try {
        Class firstClass = firstInstance.getClass();
        Method[] firstClassMethodsArr = firstClass.getMethods();

        Class secondClass = firstInstance.getClass();
        Method[] secondClassMethodsArr = secondClass.getMethods();


        for (int i = 0; i < firstClassMethodsArr.length; i++) {
            Method firstClassMethod = firstClassMethodsArr[i];
            // target getter methods.
            if(firstClassMethod.getName().startsWith("get") 
                    && ((firstClassMethod.getParameterTypes()).length == 0)
                    && (!(firstClassMethod.getName().equals("getClass")))
            ){

                Object firstValue;
                    firstValue = firstClassMethod.invoke(firstInstance, null);

                logger.info(" Value "+firstValue+" Method "+firstClassMethod.getName());

                for (int j = 0; j < secondClassMethodsArr.length; j++) {
                    Method secondClassMethod = secondClassMethodsArr[j];
                    if(secondClassMethod.getName().equals(firstClassMethod.getName())){
                        Object secondValue = secondClassMethod.invoke(secondInstance, null);
                        if(firstValue.equals(secondValue)){
                            logger.info(" Values do match! ");
                        }
                    }
                }
            }
        }
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
}

2
おかげで、しかし、リフレクションリストを使用してオブジェクトグラフを歩くコードがすでにあります。この質問は、それをどのように行うかではなく、車輪の再発明を回避しようとすることです。
ケイプロII

再発明がすでに行われていれば、車輪の再発明は悪くありません。(IE:再発明されたホイールはすでに支払われています。)
Thomas Eding 2011年

1
私はあなたに同意しますが、この場合、再発明されたコードは維持不可能な混乱です。
ケイプロII

3
Fieldsメソッドではなくチェックします。メソッドは、のように何でもかまいませんgetRandomNumber()
ボヘミアン

-3

2つのオブジェクトが異なるかどうかをすばやく判断するためのより簡単なアプローチは、ApacheCommonsライブラリを使用することです。

    BeanComparator lastNameComparator = new BeanComparator("lname");
    logger.info(" Match "+bc.compare(firstInstance, secondInstance));

どこかで変更があっただけでなく、何が変更されたかを知る必要があるため、それは私にはうまくいきません。
ケイプロII
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.