Чи існує бібліотека Java, яка може «відрізняти» два Об’єкти?


86

Чи існує бібліотека утиліт Java, яка є аналогом програми Unix diff, але для об’єктів? Я шукаю щось, що може порівнювати два об'єкти одного типу та генерувати структуру даних, яка представляє відмінності між ними (і може рекурсивно порівнювати відмінності у змінних екземпляра). Я не шукаю реалізацію 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" відрізняється між двома об'єктами, а "prop2" однаковий. Я думаю, що для DiffDataStructure найбільш природно бути деревом, але я не буду вимогливим, якщо код надійний.


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
Охайний. Врешті-решт, я також написав власну реалізацію. Я не впевнений, що отримаю можливість реально вивчити вашу реалізацію, але мені цікаво, як ви обробляєте Списки. Я закінчив обробляти всі відмінності колекцій як відмінності Карт (визначаючи ключі різними способами, залежно від того, це список, набір чи карта; потім використовуючи операції набору на keySet). Однак цей метод надмірно чутливий до змін індексу списку. Я не вирішив цю проблему, тому що мені не потрібно було для моєї програми, але мені цікаво, як ви з нею працювали (я б вважав, що це більше схоже на text-diff).
Kaypro II,

2
Ви висловлюєте хороший момент. З колекціями дійсно важко мати справу. Особливо, якщо ви підтримуєте злиття, як я. Я вирішив проблему, використовуючи ідентифікатори об’єктів, щоб визначити, чи додано, змінено чи видалено елемент (через hashCode, дорівнює та містить). Хороша річ у тому, що я можу поводитися з усіма колекціями однаково. Погана річ у тому, що я не можу належним чином обробляти (наприклад) ArrayLists, які містять один і той же об’єкт кілька разів. Я хотів би додати підтримку, але це, здається, досить складно, і, на щастя, ще ніхто про це не просив. :-)
SQiShER

Деніел, дякую за ваш коментар! Ви маєте рацію, реконструювати оригінальний об'єкт з відмінностей важко, але це в моєму випадку не надто важливо. Спочатку я зберігав попередні версії мого об’єкта в базі даних - як ви рекомендуєте. Проблема полягала в тому, що більшість частин великих документів не змінювалися, було багато дубльованих даних. Ось чому я почав шукати альтернативи і знайшов java-object-diff. Я також зазнав труднощів з колекціями / картами та змінив свої методи "рівних", щоб перевірити рівність основної бази даних (а не всього об'єкта), це допомогло.
Matthias Wuttke

39

http://javers.org - це бібліотека, яка робить саме те, що вам потрібно: має такі методи, як порівняння (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
Павел Шимчик

2
Я намагався використовувати його, але це лише для Java> = 7. Хлопці, все ще працюють люди над проектами на Java 6 !!!
jesantana

2
Що робити, якщо у двох об’єктів, які я порівнюю, є внутрішній перелік об’єктів, і я теж побачу ці відмінності? Який рівень ієрархії можна порівняти?
Deepak

1
Так, ви побачите відмінності у внутрішніх об’єктах. На даний момент відсутній поріг рівня ієрархії. Джавери підуть якомога глибше, обмеження - розмір стека.
Павел Шимчик

16

Так, бібліотека java-util має клас GraphComparator, який порівнює два графіки об'єктів Java. Він повертає різницю у вигляді списку дельт. GraphComparator також дозволяє об’єднати (застосувати) дельти. Цей код має залежності лише від JDK, інші бібліотеки відсутні.


13
Здається, це приємна бібліотека, ви повинні згадати, що ви є автором
Крістос,

3

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

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

Посилання на банку: https://github.com/sand3sh/javers-forJava6/blob/master/build/javers-forjava6.jar

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



3

Ви також можете поглянути на рішення від 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
Дякуємо, але ми вже маємо код, який обробляє графік об'єктів за допомогою відображення списків змін. Це питання не про те, як це зробити, а про те, щоб уникнути повторного винаходу колеса.
Kaypro II

Повторне винахід колеса непогане, якщо повторне винахід уже сталося. (IE: За відновлене колесо вже заплатили.)
Томас Едінг,

1
Я згоден з вами, але в цьому випадку переобладнаний код - це неможливий безлад.
Kaypro II,

3
Я б перевіряв Fieldsне методи. Методи можуть бути будь-якими, наприклад getRandomNumber().
Чеська

-3

Більш простим підходом, щоб швидко сказати, чи відрізняються два об’єкти, було б скористатися спільною бібліотекою apache

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

Це не працює для мене, тому що я повинен знати, що змінилося, а не просто те, що десь відбулися зміни.
Kaypro II
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.