Gibt es eine Java-Bibliothek, die zwei Objekte „unterscheiden“ kann?

84

Gibt es eine Java-Dienstprogrammbibliothek, die dem Unix-Programm diff entspricht, jedoch für Objekte? Ich suche nach etwas, das zwei Objekte desselben Typs vergleichen und eine Datenstruktur generieren kann, die die Unterschiede zwischen ihnen darstellt (und Unterschiede in Instanzvariablen rekursiv vergleichen kann). Ich suche keine Java-Implementierung eines Textdiff. Ich suche auch keine Hilfe bei der Verwendung von Reflexion, um dies zu tun.

Die Anwendung, die ich pflege, hat eine fragile Implementierung dieser Funktionalität, die einige schlechte Designentscheidungen hatte und die neu geschrieben werden muss, aber es wäre noch besser, wenn wir etwas von der Stange verwenden könnten.

Hier ist ein Beispiel für die Art von Dingen, nach denen ich suche:

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

Nach dem Vergleich würde mir das Dienstprogramm sagen, dass "prop1" zwischen den beiden Objekten unterschiedlich ist und "prop2" gleich ist. Ich denke, es ist für DiffDataStructure am natürlichsten, ein Baum zu sein, aber ich werde nicht wählerisch sein, wenn der Code zuverlässig ist.

Kaypro II
quelle
8
Überprüfen Sie dies, code.google.com/p/jettison
denolk
1
mögliches Duplikat von Wie
Skaffman
13
Dies ist kein Duplikat von "Wie teste ich die Gleichheit komplexer Objektgraphen?" Ich suche eine Bibliothek, keine Ratschläge dazu. Außerdem interessiert mich das Delta, nicht ob sie gleich sind oder nicht.
Kaypro II
1
Schauen Sie sich github.com/jdereg/java-util an, das über ein GraphComparator-Dienstprogramm verfügt. Diese Klasse generiert eine Liste von Deltas zwischen Objektgraphen. Darüber hinaus kann ein Delta zu einem Diagramm zusammengeführt (angewendet) werden. Tatsächlich ist es List <Delta> = GraphComparator (rootA, rootB). Sowie GraphComparator.applyDelta (rootB, List <Delta>).
John DeRegnaucourt
Ich weiß, dass Sie keine Reflexion verwenden möchten, aber das ist definitiv der einfachste / einfachste Weg, so etwas zu implementieren
RobOhRob

Antworten:

41

Könnte etwas spät sein, aber ich war in der gleichen Situation wie Sie und habe am Ende meine eigene Bibliothek für genau Ihren Anwendungsfall erstellt. Da ich gezwungen war, selbst eine Lösung zu finden, beschloss ich, sie auf Github zu veröffentlichen, um anderen die harte Arbeit zu ersparen. Sie finden es hier: https://github.com/SQiShER/java-object-diff

--- Bearbeiten ---

Hier ist ein kleines Anwendungsbeispiel basierend auf dem OP-Code:

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;
SQiShER
quelle
1
Darf ich Sie auf diesen Beitrag verweisen, der einen Anwendungsfall von Java-Object-Diff zeigt? stackoverflow.com/questions/12104969/… Vielen Dank für diese hilfreiche Bibliothek!
Matthias Wuttke
Ich habe mir Ihren Beitrag angesehen und eine Antwort geschrieben, die sich als viel zu lang herausstellte, um sie als Kommentar zu veröffentlichen. Hier ist das "Wesentliche": gist.github.com/3555555
SQiShER
2
Ordentlich. Am Ende habe ich auch meine eigene Implementierung geschrieben. Ich bin mir nicht sicher, ob ich Ihre Implementierung wirklich untersuchen kann, aber ich bin gespannt, wie Sie mit Listen umgehen. Am Ende habe ich alle Sammlungsunterschiede als Unterschiede von Maps behandelt (indem ich die Schlüssel auf unterschiedliche Weise definiert habe, je nachdem, ob es sich um eine Liste, einen Satz oder eine Karte handelt; dann habe ich Set-Operationen für das keySet verwendet). Diese Methode reagiert jedoch übermäßig empfindlich auf Änderungen des Listenindex. Ich habe dieses Problem nicht gelöst, weil ich es für meine Bewerbung nicht brauchte, aber ich bin gespannt, wie Sie damit umgegangen sind (eher wie ein Textdiff, würde ich annehmen).
Kaypro II
2
Sie sprechen einen guten Punkt an. Sammlungen sind wirklich schwer zu handhaben. Vor allem, wenn Sie das Zusammenführen unterstützen, wie ich. Ich habe das Problem gelöst, indem ich mithilfe von Objektidentitäten festgestellt habe, ob ein Element hinzugefügt, geändert oder entfernt wurde (über hashCode, gleich und enthält). Das Gute daran ist, dass ich alle Sammlungen gleich behandeln kann. Das Schlimme ist, dass ich (zum Beispiel) ArrayLists, die dasselbe Objekt mehrmals enthalten, nicht richtig behandeln kann. Ich würde gerne Unterstützung dafür hinzufügen, aber es scheint ziemlich kompliziert zu sein und zum Glück hat noch niemand danach gefragt. :-)
SQiShER
Daniel, danke für deinen Kommentar! Sie haben Recht, es ist schwierig, das ursprüngliche Objekt aus den Unterschieden zu rekonstruieren, aber dies ist in meinem Fall nicht allzu wichtig. Ursprünglich habe ich frühere Versionen meines Objekts in der Datenbank gespeichert - wie Sie empfehlen. Das Problem war, dass sich die meisten Teile der großen Dokumente nicht änderten, es gab viele doppelte Daten. Aus diesem Grund habe ich nach Alternativen gesucht und Java-Object-Diff gefunden. Ich hatte auch Schwierigkeiten mit Sammlungen / Karten und änderte meine "Gleichheits" -Methoden, um die Gleichheit der primären Datenbank (und nicht des gesamten Objekts) zu überprüfen. Dies half.
Matthias Wuttke
39

http://javers.org ist eine Bibliothek, die genau das tut, was Sie brauchen: Methoden wie compare (Object leftGraph, Object rightGraph) geben das Diff-Objekt zurück. Diff enthält eine Liste von Änderungen (ReferenceChange, ValueChange, PropertyChange), z

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

Es kann Zyklen in Diagrammen verarbeiten.

Außerdem können Sie einen Schnappschuss von jedem Diagrammobjekt erhalten. Javers verfügt über JSON-Serializer und Deserializer für Snapshots und Änderungen, sodass Sie diese problemlos in der Datenbank speichern können. Mit dieser Bibliothek können Sie einfach ein Modul für die Überwachung implementieren.

Paweł Szymczyk
quelle
1
das funktioniert nicht Wie erstellt man eine Javers-Instanz?
Ryan Vettese
2
Javers hat eine ausgezeichnete Dokumentation, die alles erklärt: javers.org
Paweł Szymczyk
2
Ich habe versucht, es zu verwenden, aber es ist nur für Java> = 7. Leute, es gibt immer noch Leute, die an Projekten auf Java 6 arbeiten !!!
Jesana
2
Was ist, wenn die beiden Objekte, die ich vergleiche, intern eine Liste von Objekten enthalten und ich diese Unterschiede auch sehe? Welche Hierarchieebene können Javer vergleichen?
Deepak
1
Ja, Sie werden Unterschiede bei internen Objekten sehen. Derzeit gibt es keinen Schwellenwert für die Hierarchieebene. Javer gehen so tief wie möglich, die Begrenzung ist die Stapelgröße.
Paweł Szymczyk
15

Ja, die Java-Util- Bibliothek verfügt über eine GraphComparator-Klasse, die zwei Java-Objektdiagramme vergleicht. Die Differenz wird als Liste der Deltas zurückgegeben. Mit dem GraphComparator können Sie auch die Deltas zusammenführen (anwenden). Dieser Code hat nur Abhängigkeiten vom JDK, keine anderen Bibliotheken.

John DeRegnaucourt
quelle
12
Scheint eine schöne Bibliothek, sollten Sie erwähnen, dass Sie ein Mitwirkender sind
Christos
3

Die gesamte Javers-Bibliothek unterstützt nur Java 7, ich war in einer Situation, da ich möchte, dass dies für ein Java 6-Projekt verwendet wird, also habe ich zufällig die Quelle genommen und geändert, so wie es für Java 6 funktioniert. Unten ist der Github-Code .

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

Jar Link: https://github.com/sand3sh/javers-forJava6/blob/master/build/javers-forjava6.jar

Ich habe nur die von Java 7 unterstützten '<>' inhärenten Cast-Konvertierungen in Java 6-Unterstützung geändert. Ich kann nicht garantieren, dass alle Funktionen funktionieren, da ich nur wenige unnötige Codes für mich kommentiert habe, die für den Vergleich aller benutzerdefinierten Objekte funktionieren.

Sandesh
quelle
2

Sie können sich auch die Lösung von Apache ansehen. Die meisten Projekte haben es bereits auf ihrem Klassenpfad, da es Teil von commons-lang ist.

Überprüfen Sie den Unterschied für bestimmte Felder:
http://commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/builder/DiffBuilder.html

Überprüfen Sie den Unterschied mithilfe von Reflection:
http://commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/builder/ReflectionDiffBuilder.html

John Blackwell
quelle
1

Je nachdem, wo Sie diesen Code verwenden, kann dies möglicherweise hilfreich oder problematisch sein. Testete diesen Code.

    /**
 * @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();
        }
}
r0ast3d
quelle
2
Vielen Dank, aber wir haben bereits Code, der das Objektdiagramm mithilfe von Reflexionslisten durchläuft. Bei dieser Frage geht es nicht darum, wie es geht, sondern darum, zu vermeiden, dass das Rad neu erfunden wird.
Kaypro II
Das Rad neu zu erfinden ist nicht schlecht, wenn die Neuerfindung bereits stattgefunden hat. (IE: Das neu erfundene Rad wurde bereits bezahlt.)
Thomas Eding
1
Ich stimme Ihnen zu, aber in diesem Fall ist der neu erfundene Code ein nicht zu wartendes Durcheinander.
Kaypro II
3
Ich würde Fieldskeine Methoden überprüfen . Methoden können alles sein, wie getRandomNumber().
Böhmisch
-3

Ein einfacherer Ansatz, um schnell festzustellen, ob zwei Objekte unterschiedlich sind, ist die Verwendung der Apache Commons-Bibliothek

    BeanComparator lastNameComparator = new BeanComparator("lname");
    logger.info(" Match "+bc.compare(firstInstance, secondInstance));
r0ast3d
quelle
Das funktioniert bei mir nicht, weil ich wissen muss, was sich geändert hat, nicht nur, dass sich irgendwo etwas geändert hat.
Kaypro II