두 개체를 자세히 비교할 수 있는 Java 유틸리티가 있습니까?
테스트의 필드 값을 기반으로 동일한 방법을 구현하지 않은 두 개체를 "심층 비교"하려면 어떻게 해야 합니까?
원본 질문(정밀성이 부족하여 SO 기준을 충족하지 못해 종결됨)은 문서화를 위해 다음과 같이 유지되었습니다.
합니다.clone()
대규모 프로젝트 내에서의 조작에 대해서, 같은 타입의 오브젝트 2개를 취득해, 상세한 비교를 실시해, 같은 타입의 오브젝트 2개가 같은지 아닌지를 판단할 수 있는 클래스가 있는지 어떤지 궁금하네요.
Unitils에는 다음 기능이 있습니다.
Java 기본값/null 값 무시 및 수집 순서 무시와 같은 다양한 옵션을 사용하여 반사를 통한 평등 어설션
이 질문 너무 좋아요!주로 그것은 거의 대답되지 않거나 나쁘게 대답되지 않기 때문이다.아직 아무도 알아내지 못한 것 같아버진 영역 :)
우선은, 이 제품을 사용할 생각조차 하지 마세요.equals
의 계약.의 계약equals
javadoc에서 정의된 바와 같이 는 동등 관계(반대형, 대칭 및 전이형)이며 동등 관계가 아닙니다.그러기 위해서는 반대대칭이어야 합니다.의 유일한 실장치는equals
는 '입니다.java.lang.Object
equals
그래프에 있는 모든 것을 비교해보면, 계약을 어길 위험이 매우 높다.Josh Bloch가 Effective Java에서 지적한 바와 같이 동등한 계약은 깨지기 쉽습니다.
동등한 계약을 유지하면서 인스턴스화 가능한 클래스를 확장하고 애스펙트를 추가하는 방법은 없습니다.
게다가 부울법이 당신에게 무슨 이득이 있나요?오리지널과 클론의 차이를 모두 캡슐화하면 좋을 것 같지 않나요?또한 그래프 내의 각 객체에 대한 비교 코드를 작성/유지하는 번거로움이 아니라 시간이 지남에 따라 소스에 따라 확장할 수 있는 것을 찾고 있다고 가정합니다.
그래서 당신이 정말로 원하는 것은 일종의 상태 비교 도구입니다.이 툴의 구현 방법은 도메인 모델의 특성과 성능 제한에 따라 달라집니다.내 경험상, 일반적인 마법 총알은 없다.그리고 많은 수의 반복에 걸쳐 느려질 것입니다.하지만 클론 작업의 완전성을 테스트하는 경우, 이 작업은 상당히 잘 수행될 것입니다.가장 좋은 옵션은 시리얼화와 리플렉션입니다.
몇 가지 문제가 발생합니다.
- 회수 순서:두 컬렉션이 동일한 객체를 보유하고 있지만 순서가 다른 경우 유사한 것으로 간주해야 합니까?
- 무시할 필드:일시적?정전기?
- 유형 동등성:필드 값은 정확히 동일한 유형이어야 합니까?아니면 한쪽이 다른쪽을 연장해도 될까요?
- 더 있는데 까먹었어...
XStream XMLUnit은 처음할 수도 있기 때문에 .XMLUnit은 모든 차이를 보고할 수도 있고 처음 발견한 차이에서 멈출 수도 있기 때문에 좋습니다.출력에는 다른 노드에 대한 xpath가 포함되어 있어 좋습니다.기본적으로 정렬되지 않은 수집은 허용하지 않지만 정렬되도록 구성할 수 있습니다. 핸들러 a difference handler)DifferenceListener
등할 수 를 사용하면 순서 무시 등 차이에 대처하는 방법을 지정할 수 있습니다.그러나 가장 간단한 커스터마이즈를 넘어서는 작업을 수행하려고 하면 쓰기 어려워지고 세부 정보가 특정 도메인 개체에 구속되는 경향이 있습니다.
제 개인적인 취향은 반사를 사용하여 선언된 모든 필드를 순환하고 각각의 필드를 드릴다운하여 이동하면서 차이를 추적하는 것입니다. 예외를원하지 한스택 오버플로 예외를 원하지 않는 한 재귀는 사용하지 마십시오.(「」를합니다.LinkedList
뭐 그런 거)보통 과도 필드 및 정적 필드는 무시하고 이미 비교한 개체 쌍은 건너뛰기 때문에 누군가가 자기 참조 코드를 작성하기로 결정해도 무한 루프 상태가 되지 않습니다(단, 동일한 개체 참조가 재사용되는 경우가 많기 때문에 항상 원시 래퍼를 비교합니다).수집 순서를 무시하고 특정 유형이나 필드를 무시하도록 미리 설정할 수 있지만 주석을 통해 필드 자체에 대한 상태 비교 정책을 정의하는 것이 좋습니다.이것이 바로 IMHO로, 클래스에 대한 메타 데이터를 런타임에 사용할 수 있도록 하기 위한 주석의 목적입니다.예를 들어 다음과 같습니다.
@StatePolicy(unordered=true, ignore=false, exactTypesOnly=true)
private List<StringyThing> _mylist;
사실 이건 정말 어려운 문제라고 생각하지만, 완전히 해결할 수 있어!그리고, 자신에게 맞는 것이 있으면, 그것은 정말로 편리합니다.
행운을 빌어요.그리고 만약 여러분이 정말 천재적인 무언가를 생각해 낸다면, 공유하는 것을 잊지 마세요!
AssertJ에서는 다음 작업을 수행할 수 있습니다.
Assertions.assertThat(expectedObject).isEqualToComparingFieldByFieldRecursively(actualObject);
아마 모든 경우에 효과가 있는 것은 아닐지도 모르지만, 여러분이 생각하는 것보다 더 많은 경우에 효과가 있을 것입니다.
이 문서의 내용은 다음과 같습니다.
속성/필드 비교에 의한 재귀적인 속성/필드(상속된 것 포함)를 바탕으로 테스트 대상 객체(실제)가 지정된 객체와 동일하다고 주장합니다.이 기능은 실제 구현과 동일한 구현이 적합하지 않을 때 유용합니다.사용자 지정 동등 구현이 있는 필드에는 재귀 속성/필드 비교가 적용되지 않습니다. 즉, 필드별 비교 대신 재정의된 동등 메서드가 사용됩니다.
재귀 비교는 사이클을 처리합니다.기본적으로 플로트는 1.0E-6의 정밀도와 비교되며 1.0E-15의 두 배입니다.
Comparator For Fields(Comparator, String...) 및 Comparator For Type(Comparator, Class)을 사용하여 각각 필드 또는 유형별로 커스텀 비교기를 지정할 수 있습니다.
비교할 개체의 유형은 다를 수 있지만 속성/필드는 같아야 합니다.예를 들어 실제 객체에 String 필드가 있으면 다른 객체에도 필드가 있어야 합니다.객체에 필드가 있고 이름이 같은 속성이 있는 경우 속성 값이 필드에 사용됩니다.
java-http://https://github.com/jdereg/java-util 내의 DeepEquals 및 DeepHashCode()를 참조하십시오.
이 클래스는 원래 작성자가 요청한 대로 수행합니다.
equals() 메서드 덮어쓰기
다음과 같이 EqualsBuilder.reflectionEquals()를 사용하여 클래스의 equals() 메서드를 덮어쓸 수 있습니다.
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
Hibernate Envers에 의해 수정된 두 엔티티 인스턴스의 비교를 구현해야 했습니다.저는 저만의 차이점을 쓰기 시작했지만, 다음 틀을 발견했습니다.
https://github.com/SQiShER/java-object-diff
동일한 유형의 두 개체를 비교할 수 있으며 변경, 추가 및 제거가 표시됩니다.변경이 없으면 오브젝트는 동일합니다(이론적으로는).검사 중에 무시해야 하는 getter에 대한 주석이 제공됩니다.프레임 작업은 동등성 검사보다 훨씬 더 광범위하게 적용됩니다.를 사용하여 변경 로그를 생성하고 있습니다.
성능은 정상입니다.JPA 엔티티를 비교할 때는 먼저 엔티티 매니저에서 엔티티를 분리해야 합니다.
XStream에 있습니다.
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
XStream xstream = new XStream();
String oxml = xstream.toXML(o);
String myxml = xstream.toXML(this);
return myxml.equals(oxml);
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
XStream xstream = new XStream();
String myxml = xstream.toXML(this);
return myxml.hashCode();
}
http://www.unitils.org/tutorial-reflectionassert.html
public class User {
private long id;
private String first;
private String last;
public User(long id, String first, String last) {
this.id = id;
this.first = first;
this.last = last;
}
}
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);
Hamcrest에는 Matcher samePropertyValuesAs가 있습니다.그러나 JavaBeans 규약에 의존합니다(getter와 setter 사용).비교할 객체에 속성에 대한 getter와 setter가 없는 경우 이 기능은 작동하지 않습니다.
import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
import static org.junit.Assert.assertThat;
import org.junit.Test;
public class UserTest {
@Test
public void asfd() {
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertThat(user1, samePropertyValuesAs(user2)); // all good
user2 = new User(1, "John", "Do");
assertThat(user1, samePropertyValuesAs(user2)); // will fail
}
}
사용자 bean - getters 및 setters 포함
public class User {
private long id;
private String first;
private String last;
public User(long id, String first, String last) {
this.id = id;
this.first = first;
this.last = last;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
오브젝트가 Serializable을 실장하고 있는 경우는, 다음을 사용할 수 있습니다.
public static boolean deepCompare(Object o1, Object o2) {
try {
ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
ObjectOutputStream oos1 = new ObjectOutputStream(baos1);
oos1.writeObject(o1);
oos1.close();
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
ObjectOutputStream oos2 = new ObjectOutputStream(baos2);
oos2.writeObject(o2);
oos2.close();
return Arrays.equals(baos1.toByteArray(), baos2.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Linked List 예는 그리 어렵지 않습니다.코드가 2개의 오브젝트 그래프를 통과할 때 방문한 오브젝트를 세트 또는 맵에 배치합니다.다른 개체 참조로 이동하기 전에 이 세트가 테스트되어 개체가 이미 통과되었는지 확인합니다.만약 그렇다면, 더 이상 갈 필요가 없습니다.
Linked List를 사용한다고 말한 사람(Stack과 같지만 동기화된 메서드가 없기 때문에 더 빠릅니다)에 동의합니다.스택을 사용하여 객체 그래프를 이동하고 반사를 사용하여 각 필드를 얻는 것이 이상적인 솔루션입니다.이 "external" equals()와 "external" hashCode()는 모든 equals() 메서드와 hashCode() 메서드가 호출해야 하는 것입니다.이제 customer equals() 메서드는 필요 없습니다.
구글 코드에 있는 완전한 객체 그래프를 가로지르는 코드를 작성했습니다.json-io(http://code.google.com/p/json-io/)를 참조해 주세요.Java 객체그래프를 JSON으로 시리얼화하고 거기서 역직렬화합니다.퍼블릭 컨스트럭터 유무, 시리얼화 가능 또는 시리얼화 불가능 등의 모든 Java 객체를 처리합니다.이 통과 코드는 외부 "equals()" 및 외부 "hashcode()" 구현의 기반이 됩니다.참고로 JsonReader/JsonWriter(json-io)는 보통 내장된 ObjectInputStream/ObjectOutputStream보다 빠릅니다.
이 JsonReader / JsonWriter는 비교에 사용할 수 있지만 해시 코드에는 도움이 되지 않습니다.유니버설 해시 코드() 및 등가()를 원하는 경우 자체 코드가 필요합니다.일반 그래프 방문자라면 이 일을 해낼 수 있을 것 같습니다.곧 알게 되겠지.
기타 고려사항(static 필드)은 모든 인스턴스에서 공유되므로 모든 equals() 인스턴스는 static 필드의 값이 같기 때문에 건너뛸 수 있습니다.
과도 필드의 경우 - 선택 가능한 옵션입니다.때로는 과도현상을 카운트하지 않을 수도 있습니다."때로는 미친 사람처럼 느껴질 때도 있고 그렇지 않을 때도 있어요."
(다른 프로젝트의 경우) json-io 프로젝트로 돌아가면 equals() / hashcode() 프로젝트를 찾을 수 있습니다.아직 이름은 모르지만 분명 알 수 있을 거야.
Ray Hulha 솔루션에서 영감을 얻은 가장 쉬운 솔루션은 개체를 직렬화한 다음 원시 결과를 자세히 비교하는 것이라고 생각합니다.
시리얼화는 바이트, json, xml 또는 단순 toString 등입니다.ToString이 더 싼 것 같아요.Lombok은 우리를 위해 쉽게 커스터마이즈할 수 있는 무료 ToSTring을 생성합니다.아래의 예를 참조해 주세요.
@ToString @Getter @Setter
class foo{
boolean foo1;
String foo2;
public boolean deepCompare(Object other) { //for cohesiveness
return other != null && this.toString().equals(other.toString());
}
}
너도 알겠지만, 이론상으로는 항상 .equals를 덮어쓰고 두 물체가 실제로 동일하다고 주장해야 해.이는 해당 멤버가 오버라이드된 .equals 메서드를 체크하는 것을 의미합니다.
이러한 이유로 .equals가 오브젝트에 정의됩니다.
이 작업을 꾸준히 수행한다면 문제가 없을 것입니다.
그러한 상세한 비교에 대한 보증을 중단하는 것은 문제가 될 수 있다.다음은 어떻게 해야 합니까? (이러한 비교기를 구현하면 좋은 단위 테스트가 될 것입니다.)
LinkedListNode a = new LinkedListNode();
a.next = a;
LinkedListNode b = new LinkedListNode();
b.next = b;
System.out.println(DeepCompare(a, b));
여기 또 있습니다.
LinkedListNode c = new LinkedListNode();
LinkedListNode d = new LinkedListNode();
c.next = d;
d.next = c;
System.out.println(DeepCompare(c, d));
Apache는 두 개체를 모두 문자열로 변환하고 문자열을 비교합니다. 단, 덮어쓰기 toString()이 필요합니다.
obj1.toString().equals(obj2.toString())
toString()의 덮어쓰기
모든 필드가 원시 유형인 경우:
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
@Override
public String toString() {return
ReflectionToStringBuilder.toString(this);}
원시 필드 및/또는 수집 및/또는 맵이 아닌 경우:
// Within class
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
@Override
public String toString() {return
ReflectionToStringBuilder.toString(this,new
MultipleRecursiveToStringStyle());}
// New class extended from Apache ToStringStyle
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.*;
public class MultipleRecursiveToStringStyle extends ToStringStyle {
private static final int INFINITE_DEPTH = -1;
private int maxDepth;
private int depth;
public MultipleRecursiveToStringStyle() {
this(INFINITE_DEPTH);
}
public MultipleRecursiveToStringStyle(int maxDepth) {
setUseShortClassName(true);
setUseIdentityHashCode(false);
this.maxDepth = maxDepth;
}
@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
}
@Override
protected void appendDetail(StringBuffer buffer, String fieldName,
Collection<?> coll) {
for(Object value: coll){
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
}
}
@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Map<?, ?> map) {
for(Map.Entry<?,?> kvEntry: map.entrySet()){
Object value = kvEntry.getKey();
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
value = kvEntry.getValue();
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
}
}}
언급URL : https://stackoverflow.com/questions/1449001/is-there-a-java-utility-to-do-a-deep-comparison-of-two-objects
'programing' 카테고리의 다른 글
부모 클래스의 자식 클래스 이름 가져오기(정적 컨텍스트) (0) | 2023.01.15 |
---|---|
jQuery가 비동기 Ajax 요청이 아닌 동기 Ajax 요청을 수행하도록 하려면 어떻게 해야 합니까? (0) | 2023.01.15 |
변수 이름을 문자열로 가져오기 (0) | 2023.01.15 |
로그인 폼에 CSRF 공격에 대한 토큰이 필요합니까? (0) | 2023.01.15 |
스크립트 로드 및 실행 순서 (0) | 2023.01.15 |