Czy istnieje biblioteka Java, która może „porównywać” dwa obiekty?

86

Czy istnieje biblioteka narzędzi Java, która jest analogiczna do programu różnicowego w systemie Unix, ale dla obiektów? Szukam czegoś, co może porównać dwa obiekty tego samego typu i wygenerować strukturę danych, która reprezentuje różnice między nimi (i może rekurencyjnie porównywać różnice w zmiennych instancji). Ja nie szukam implementacji Java diff tekstowym. Ja również nie szuka pomocy, jak korzystać z refleksji, aby to zrobić.

Aplikacja, którą utrzymuję, ma delikatną implementację tej funkcji, która miała słabe wybory projektowe i którą trzeba przepisać, ale byłoby jeszcze lepiej, gdybyśmy mogli użyć czegoś z półki.

Oto przykład tego, czego szukam:

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

Po porównaniu narzędzie powiedziałoby mi, że „prop1” różni się między dwoma obiektami, a „prop2” jest tym samym. Myślę, że to najbardziej naturalne, że DiffDataStructure jest drzewem, ale nie będę wybredny, jeśli kod jest niezawodny.

Kaypro II
źródło
8
sprawdź to, code.google.com/p/jettison
denolk
13
To nie jest duplikat „Jak sprawdzić równość złożonych grafów obiektowych?” Szukam biblioteki, a nie porady, jak to zrobić. Ponadto interesuje mnie delta, a nie to, czy są równe, czy nie.
Kaypro II
1
Spójrz na github.com/jdereg/java-util, który ma narzędzia GraphComparator. Ta klasa wygeneruje listę różnic między grafami obiektów. Ponadto może łączyć (stosować) deltę do wykresu. W rzeczywistości jest to List <Delta> = GraphComparator (rootA, rootB). Jak również GraphComparator.applyDelta (rootB, List <Delta>).
John DeRegnaucourt
Wiem, że nie chcesz używać odbicia, ale to zdecydowanie najłatwiejszy / najprostszy sposób na zaimplementowanie czegoś takiego
RobOhRob

Odpowiedzi:

42

Może się trochę spóźnić, ale byłem w takiej samej sytuacji jak ty i ostatecznie stworzyłem własną bibliotekę dla dokładnie twojego przypadku użycia. Ponieważ musiałem sam wymyślić rozwiązanie, zdecydowałem się opublikować je na Githubie, aby oszczędzić innym ciężkiej pracy. Możesz go znaleźć tutaj: https://github.com/SQiShER/java-object-diff

--- Edytuj ---

Oto mały przykład użycia oparty na kodzie 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;
SQiShER
źródło
1
Czy mogę wskazać ten post przedstawiający przypadek użycia java-object-diff? stackoverflow.com/questions/12104969/… Wielkie dzięki za tę pomocną bibliotekę!
Matthias Wuttke
Przyjrzałem się
Twojemu postowi
2
Schludny. Skończyło się również na napisaniu własnej implementacji. Nie jestem pewien, czy będę miał szansę naprawdę zbadać twoją implementację, ale jestem ciekawy, jak radzisz sobie z listami. Skończyło się na tym, że traktowałem wszystkie różnice kolekcji jako różnice map (definiując klucze na różne sposoby, w zależności od tego, czy jest to lista, zestaw czy mapa; następnie używając operacji na zestawie kluczy). Jednak ta metoda jest nadmiernie wrażliwa na zmiany indeksu listy. Nie rozwiązałem tego problemu, ponieważ nie musiałem tego robić w mojej aplikacji, ale jestem ciekawy, jak sobie z tym poradziłeś (zakładam, że bardziej przypomina to różnicę tekstową).
Kaypro II
2
Poruszasz dobry punkt widzenia. Kolekcje są naprawdę trudne w obsłudze. Zwłaszcza jeśli wspierasz łączenie, tak jak ja. Rozwiązałem problem, używając tożsamości obiektów do wykrywania, czy element został dodany, zmieniony lub usunięty (za pomocą hashCode, equals i zawiera). Dobrą rzeczą jest to, że wszystkie kolekcje mogę traktować tak samo. Złe jest to, że nie mogę poprawnie obsłużyć (na przykład) ArrayLists, które zawierają ten sam obiekt wiele razy. Chciałbym dodać obsługę tego, ale wydaje się to dość skomplikowane i na szczęście nikt jeszcze o to nie prosił. :-)
SQiShER
Daniel, dzięki za komentarz! Masz rację, rekonstrukcja oryginalnego obiektu z różnic jest trudna, ale w moim przypadku nie jest to zbyt ważne. Pierwotnie przechowywałem poprzednie wersje mojego obiektu w bazie danych - tak jak radzisz. Problem polegał na tym, że większość części dużych dokumentów nie uległa zmianie, było dużo zduplikowanych danych. Dlatego zacząłem szukać alternatyw i znalazłem java-object-diff. Doświadczyłem również trudności ze zbiorami / mapami i zmieniłem moje metody „równych”, aby sprawdzić równość podstawowej bazy danych (a nie całego obiektu), to pomogło.
Matthias Wuttke
39

http://javers.org to biblioteka, która robi dokładnie to, czego potrzebujesz: zawiera metody takie jak compare (Object leftGraph, Object rightGraph) zwracające obiekt Diff. Diff zawiera listę zmian (ReferenceChange, ValueChange, PropertyChange) np

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

Może obsługiwać cykle na wykresach.

Dodatkowo możesz uzyskać migawkę dowolnego obiektu wykresu. Javers ma serializatory JSON i deserializatory do migawki i zmiany, dzięki czemu można je łatwo zapisać w bazie danych. Dzięki tej bibliotece możesz łatwo wdrożyć moduł do audytu.

Paweł Szymczyk
źródło
1
to nie działa. jak utworzyć instancję Javers?
Ryan Vettese
2
Javers ma doskonałą dokumentację, która wszystko wyjaśnia: javers.org
Paweł Szymczyk
2
Próbowałem go użyć, ale jest to tylko dla Java> = 7. Chłopaki, wciąż są ludzie pracujący nad projektami na Javie 6 !!!
jesantana
2
A co, jeśli dwa obiekty, które porównuję, mają wewnętrzną listę obiektów i czy ja też będę widzieć te różnice? Jaki poziom hierarchii mogliby porównać javers?
Deepak
1
Tak, zobaczysz różnice w obiektach wewnętrznych. W tej chwili nie ma progu dla poziomu hierarchii. Javers wejdą tak głęboko, jak to możliwe, ograniczeniem jest rozmiar stosu.
Paweł Szymczyk
15

Tak, biblioteka java-util zawiera klasę GraphComparator, która porównuje dwa wykresy obiektów Java. Zwraca różnicę w postaci listy delt. GraphComparator pozwala również na scalanie (stosowanie) delt. Ten kod ma zależności tylko od JDK, żadnych innych bibliotek.

John DeRegnaucourt
źródło
12
Wygląda na fajną bibliotekę, powinieneś wspomnieć, że jesteś współpracownikiem
Christos,
3

Cała biblioteka Javers obsługuje tylko Java 7, byłem w sytuacji, ponieważ chcę, aby była używana w projekcie Java 6, więc zdarzyło mi się wziąć źródło i zmienić sposób, w jaki działa dla Java 6 poniżej jest kod github .

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

Link do słoika: https://github.com/sand3sh/javers-forJava6/blob/master/build/javers-forjava6.jar

Zmieniłem tylko nieodłączne konwersje rzutowania obsługiwane przez Javę 7 na obsługę Java 6 Nie gwarantuję, że wszystkie funkcje będą działać, ponieważ skomentowałem kilka niepotrzebnych dla mnie kodów, które działają dla wszystkich porównań obiektów niestandardowych.

Sandesh
źródło
Link do słoika
Sandesh
2

Możesz również rzucić okiem na rozwiązanie z Apache. Większość projektów ma go już na swojej ścieżce klas, ponieważ jest częścią wspólnego języka.

Sprawdź różnice dla określonych pól:
http://commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/builder/DiffBuilder.html

Sprawdź różnicę za pomocą refleksji:
http://commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/builder/ReflectionDiffBuilder.html

John Blackwell
źródło
1

Może to pomoże, w zależności od tego, gdzie używasz tego kodu, może być przydatne lub problematyczne. Przetestowałem ten kod.

    /**
 * @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
źródło
2
Dzięki, ale mamy już kod, który przechodzi po grafie obiektu za pomocą odbicia, a lista zmian. To pytanie nie dotyczy tego, jak to zrobić, ale próba uniknięcia ponownego wynalezienia koła.
Kaypro II
Ponowne wynalezienie koła nie jest złe, jeśli odkrycie już się wydarzyło. (IE: wynalezione na nowo koło zostało już opłacone.)
Thomas Eding,
1
Zgadzam się z tobą, ale w tym przypadku wymyślony na nowo kod to nie do utrzymania bałagan.
Kaypro II
3
Sprawdzałbym Fieldsnie metody. Metody mogą być dowolne getRandomNumber().
Bohemian
-3

Prostszym podejściem do szybkiego stwierdzenia, czy dwa obiekty są różne, byłoby skorzystanie z biblioteki Apache commons

    BeanComparator lastNameComparator = new BeanComparator("lname");
    logger.info(" Match "+bc.compare(firstInstance, secondInstance));
r0ast3d
źródło
To nie działa dla mnie, ponieważ muszę wiedzieć, co się zmieniło, a nie tylko, że gdzieś nastąpiła zmiana.
Kaypro II