두 객체를 "차별"할 수있는 Java 라이브러리가 있습니까?


90

Unix 프로그램 diff와 유사한 Java 유틸리티 라이브러리가 있지만 Objects 용입니까? 동일한 유형의 두 개체를 비교하고 그 차이를 나타내는 데이터 구조를 생성 할 수있는 무언가를 찾고 있습니다 (그리고 인스턴스 변수의 차이를 재귀 적으로 비교할 수 있음). 난 하지 텍스트 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가 트리가되는 것이 가장 자연스러운 일이라고 생각하지만 코드가 신뢰할 수 있다면 까다 롭지 않을 것입니다.


9
이것을 확인하십시오, code.google.com/p/jettison
denolk 2011


14
이것은 "복잡한 개체 그래프의 동등성을 테스트하는 방법?"의 중복이 아닙니다. 나는 그것을하는 방법에 대한 조언이 아니라 도서관을 찾고 있습니다. 또한 델타가 같거나 같지 않은지 여부가 아니라 델타에 관심이 있습니다.
Kaypro II 2011

1
github.com/jdereg/java-util GraphComparator 유틸리티가 있습니다. 이 클래스는 개체 그래프 사이의 델타 목록을 생성합니다. 또한 델타를 그래프에 병합 (적용) 할 수 있습니다. 효과적으로 List <Delta> = GraphComparator (rootA, rootB)입니다. 뿐만 아니라 GraphComparator.applyDelta (rootB, List <Delta>).
John DeRegnaucourt

당신이 리플렉션을 사용하고 싶지 않다는 것을 알고 있습니다.하지만 이것은 확실히 이와 같은 것을 구현하는 가장 쉽고 간단한 방법입니다
RobOhRob

답변:


44

조금 늦었을 수도 있지만, 저도 당신과 같은 상황에 있었고 결국 당신의 사용 사례에 맞는 저만의 라이브러리를 만들었습니다. 내가 직접 해결책을 찾아야했기 때문에 다른 사람들의 노력을 아끼기 위해 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

나는 당신의 게시물을 봤는데 댓글로 게시하기에 너무 긴 답변을 썼습니다. 그래서 여기에 "요점"이 있습니다 : gist.github.com/3555555
SQiShER

2
산뜻한. 나는 내 자신의 구현도 작성했습니다. 구현을 실제로 살펴볼 기회를 얻을 수 있을지 모르겠지만 목록을 어떻게 처리하는지 궁금합니다. 결국 모든 컬렉션 차이를 Maps의 차이로 처리했습니다 (목록, 집합 또는지도인지에 따라 다른 방식으로 키를 정의한 다음 keySet에서 집합 작업을 사용하여). 그러나이 방법은 목록 인덱스 변경에 지나치게 민감합니다. 내 응용 프로그램에 필요하지 않았기 때문에 그 문제를 해결하지 못했지만 어떻게 처리했는지 궁금합니다 (텍스트 차이와 비슷합니다).
Kaypro II

2
당신은 좋은 지적을합니다. 컬렉션은 처리하기 정말 어렵습니다. 특히 나처럼 병합을 지원하는 경우. 객체 ID를 사용하여 항목이 추가, 변경 또는 제거되었는지 여부를 감지하여 문제를 해결했습니다 (hashCode, equals 및 contains를 통해). 좋은 점은 모든 컬렉션을 동일하게 취급 할 수 있다는 것입니다. 나쁜 점은 동일한 객체를 여러 번 포함하는 ArrayLists 등을 제대로 처리 할 수 ​​없다는 것입니다. 이에 대한 지원을 추가하고 싶지만 매우 복잡하고 다행히 아무도 요청하지 않았습니다. :-)
SQiShER

다니엘, 귀하의 의견에 감사드립니다! 당신 말이 맞아요, diff에서 원본 객체를 재구성하는 것은 어렵지만 제 경우에는 그렇게 중요하지 않습니다. 원래는 사용자가 권장하는대로 이전 버전의 개체를 데이터베이스에 저장했습니다. 문제는 큰 문서의 대부분이 변경되지 않고 중복 된 데이터가 많다는 것입니다. 이것이 내가 대안을 찾기 시작했고 java-object-diff를 찾은 이유입니다. 또한 컬렉션 / 맵에 어려움을 겪었고 데이터베이스 기본 (전체 객체가 아닌)의 동등성을 확인하기 위해 "equals"메소드를 변경했습니다. 이것은 도움이되었습니다.
마티아스 Wuttke

40

http://javers.org 는 필요한 것을 정확히 수행하는 라이브러리입니다 : Diff 객체를 반환하는 compare (Object leftGraph, Object rightGraph)와 같은 메소드가 있습니다. 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-06-18

2
: Javers 모든 것을 설명 훌륭한 문서가 javers.org
파블 Szymczyk

2
나는 그것을 사용하려고했지만 Java> = 7에만 해당됩니다. 여러분, 아직 Java 6에서 프로젝트 작업을하는 사람들이 있습니다 !!!
jesantana

2
비교하는 두 개체에 내부적으로 개체 목록이 있고 이러한 차이점도 볼 수 있다면 어떻게됩니까? javers가 비교할 수있는 계층 수준은 무엇입니까?
Deepak

1
예, 내부 개체의 차이점을 볼 수 있습니다. 현재 계층 수준에 대한 임계 값은 없습니다. Javers는 가능한 한 깊숙이 들어갈 것이며 한계는 스택 크기입니다.
Paweł Szymczyk

16

예, java-util 라이브러리에는 두 개의 Java Object Graph를 비교하는 GraphComparator 클래스가 있습니다. 차이를 델타 목록으로 반환합니다. GraphComparator를 사용하면 델타도 병합 (적용) 할 수 있습니다. 이 코드는 JDK에만 의존하고 다른 라이브러리에는 없습니다.


13
, 당신은 당신이 기여 것을 멋진 라이브러리 언급해야 같은데
크리스토스

4

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


이것이 받아 들여진 대답이어야합니다!
Janaka Bandara

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 지원으로 변경했습니다. 모든 사용자 지정 개체 비교에서 작동하는 불필요한 코드를 거의 언급하지 않았기 때문에 모든 기능이 작동한다고 보장하지는 않습니다.



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 2011

재창조가 이미 일어났다면 바퀴를 재창조하는 것은 나쁘지 않습니다. (IE : 재창조 된 바퀴는 이미 지불되었습니다.)
Thomas Eding

1
동의하지만이 경우에 재발 명 된 코드는 유지 관리가 불가능합니다.
Kaypro II 2011

3
나는 Fields방법이 아닌 것을 검사 할 것 입니다. 메소드는 getRandomNumber().
보헤미안

-3

두 객체가 다른지 빠르게 알려주는 더 간단한 방법은 아파치 공용 라이브러리를 사용하는 것입니다.

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

어딘가에 변화가 있다는 것만이 아니라 무엇이 바뀌 었는지 알아야하기 때문에 그것은 저에게 효과가 없습니다.
Kaypro II
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.