Есть ли библиотека Java, которая может «различать» два объекта?

86

Есть ли служебная библиотека Java, аналогичная программе diff для Unix, но для объектов? Я ищу что-то, что может сравнивать два объекта одного типа и генерировать структуру данных, которая представляет различия между ними (и может рекурсивно сравнивать различия в переменных экземпляра). Я не ищу Java-реализацию текстового diff. Я также не ищу помощи в том, как использовать для этого отражение.

Приложение, которое я поддерживаю, имеет хрупкую реализацию этой функциональности, в которой были некоторые неудачные варианты дизайна и которую необходимо переписать, но было бы еще лучше, если бы мы могли использовать что-то готовое.

Вот пример того, что я ищу:

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» у этих двух объектов различается, а «prop2» - то же самое. Я думаю, что для DiffDataStructure наиболее естественно быть деревом, но я не буду привередничать, если код надежен.

Кайпро II
источник
8
проверьте это, code.google.com/p/jettison
denolk
13
Это не дубликат «Как проверить равенство графов сложных объектов?» Ищу библиотеку, а не совет, как это сделать. Кроме того, меня интересует дельта, а не равны они или нет.
Kaypro II
1
Посмотрите на github.com/jdereg/java-util, где есть утилиты GraphComparator. Этот класс сгенерирует список дельты между графами объектов. Кроме того, он может объединять (применять) дельту к графу. Фактически это List <Delta> = GraphComparator (rootA, rootB). А также GraphComparator.applyDelta (rootB, List <Delta>).
John DeRegnaucourt
Я знаю, что вы не хотите использовать отражение, но это определенно самый простой / простой способ реализовать что-то вроде этого
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/… Большое спасибо за эту полезную библиотеку!
Маттиас Вуттке,
Я просмотрел ваше сообщение и написал ответ, который оказался слишком длинным для публикации в качестве комментария, так что вот его «суть»: gist.github.com/3555555
SQiShER
2
Аккуратно. В итоге я написал свою собственную реализацию. Я не уверен, что у меня будет возможность по-настоящему изучить вашу реализацию, но мне любопытно, как вы обрабатываете списки. В итоге я обработал все различия коллекций как различия карт (определяя ключи по-разному, в зависимости от того, является ли это список, набор или карта; затем используя операции набора для набора ключей). Однако этот метод слишком чувствителен к изменениям индекса списка. Я не решил эту проблему, потому что мне это не нужно для моего приложения, но мне любопытно, как вы с этим справились (скорее, как text-diff, я полагаю).
Kaypro II
2
Вы поднимаете хороший вопрос. Коллекции действительно сложно иметь дело. Особенно, если вы поддерживаете слияние, как я. Я решил проблему, используя идентификаторы объектов, чтобы определить, был ли элемент добавлен, изменен или удален (с помощью hashCode, равно и содержит). Хорошо то, что я могу относиться ко всем коллекциям одинаково. Плохо то, что я не могу правильно обрабатывать (например) списки ArrayLists, которые содержат один и тот же объект несколько раз. Я бы хотел добавить поддержку для этого, но это кажется довольно сложным, и, к счастью, об этом пока никто не просил. :-)
SQiShER 05
Даниэль, спасибо за комментарий! Вы правы, восстановить исходный объект по диффам сложно, но в моем случае это не слишком важно. Изначально я хранил предыдущие версии своего объекта в базе данных - как вы рекомендуете. Проблема заключалась в том, что большая часть больших документов не менялась, было много дублированных данных. Вот почему я начал искать альтернативы и нашел java-object-diff. У меня также возникли трудности с коллекциями / картами, и я изменил свои методы "equals", чтобы проверить равенство первичной базы данных (а не всего объекта), это помогло.
Маттиас Вуттке,
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
2
У Javers есть отличная документация, которая все объясняет: javers.org
Paweł Szymczyk
2
Я пробовал использовать его, но только для Java> = 7. Ребята, еще есть люди, работающие над проектами на Java 6 !!!
jesantana
2
Что, если два сравниваемых объекта имеют внутренний список объектов, и увижу ли я эти различия? Какой уровень иерархии мог бы сравнить джаверы?
Deepak
1
Да, вы увидите различия на внутренних объектах. На данный момент нет порога для уровня иерархии. Джаверс пойдет настолько глубоко, насколько это возможно, ограничение - размер стека.
Paweł Szymczyk
15

Да, в библиотеке java-util есть класс GraphComparator, который будет сравнивать два графа объектов Java. Он возвращает разницу в виде списка дельт. GraphComparator также позволяет вам объединять (применять) дельты. Этот код зависит только от JDK, а не от других библиотек.

Джон ДеРегнокур
источник
12
Кажется, хорошая библиотека, вы должны упомянуть, что являетесь соавтором
Христос
3

Вся библиотека Javers поддерживает только Java 7, я был в ситуации, так как хочу, чтобы это использовалось для проекта Java 6, поэтому я случайно взял исходный код и изменил способ, которым он работает для Java 6, ниже - это код github .

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

Ссылка на Jar: https://github.com/sand3sh/javers-forJava6/blob/master/build/javers-forjava6.jar

Я только изменил встроенные преобразования приведения типов '<>', поддерживаемые Java 7, на поддержку Java 6. Я не гарантирую, что все функции будут работать, так как я прокомментировал несколько ненужных кодов для меня, он работает для сравнения всех настраиваемых объектов.

Сандеш
источник
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();
        }
}
r0ast3d
источник
2
Спасибо, но у нас уже есть код, который обходит граф объекта с помощью отражения и перечисляет изменения. Это вопрос не о том, как это сделать, а о попытках избежать повторного изобретения колеса.
Kaypro II
Изобретать колесо - неплохо, если изобретение уже произошло. (IE: за заново изобретенное колесо уже заплатили.)
Томас Эдинг
1
Я согласен с вами, но в этом случае заново изобретенный код представляет собой неразрешимый беспорядок.
Kaypro II
3
Я бы проверял Fieldsне методы. Методы могут быть любыми, вроде getRandomNumber().
Bohemian
-3

Более простой подход, чтобы быстро определить, отличаются ли два объекта, - это использовать библиотеку общего пользования apache.

    BeanComparator lastNameComparator = new BeanComparator("lname");
    logger.info(" Match "+bc.compare(firstInstance, secondInstance));
r0ast3d
источник
Для меня это не работает, потому что мне нужно знать, что изменилось, а не только то, что где-то было изменение.
Kaypro II 03