두 개체의 심층 비교를 수행하는 Java 리플렉션 유틸리티가 있습니까?


99

clone()대규모 프로젝트 내 에서 다양한 작업에 대한 단위 테스트를 작성하려고하는데 같은 유형의 두 개체를 가져 와서 깊은 비교를 수행 할 수있는 기존 클래스가 어딘가에 있는지 궁금합니다. 똑같은가요?


1
이 클래스는 객체 그래프의 특정 지점에서 동일한 객체를 받아 들일 수 있는지 아니면 동일한 참조 만 받아 들일 수 있는지 어떻게 알 수 있습니까?
Zed

이상적으로는 충분히 구성 할 수있을 것입니다. :) 새 필드가 추가되고 복제되지 않으면 테스트에서 식별 할 수 있도록 자동으로 무언가를 찾고 있습니다.
Uri

3
내가 말하려는 것은 어쨌든 비교를 구성 (즉 구현)해야한다는 것입니다. 그렇다면 클래스에서 equals 메소드를 재정의하고 사용하지 않는 이유는 무엇입니까?
Zed

3
equals가 크고 복잡한 객체에 대해 false를 반환하면 어디서부터 시작합니까? 객체를 여러 줄 문자열로 바꾸고 문자열 비교를 수행하는 것이 훨씬 낫습니다. 그러면 두 개체가 다른 곳을 정확히 볼 수 있습니다. IntelliJ는 두 결과 간의 여러 변경 사항을 찾는 데 도움이되는 "변경"비교 창을 표시합니다. 즉, assertEquals (string1, string2)의 출력을 이해하고 비교 창을 제공합니다.
Peter Lawrey

정말 좋은 대답은 허용 하나, 게다가 매장 얻었을 것 같다가 여기에있다
user1445967

답변:


62

Unitils 에는 다음과 같은 기능이 있습니다.

Java 기본값 / 널 값 무시 및 컬렉션 순서 무시와 같은 다양한 옵션을 사용하여 리플렉션을 통한 평등 주장


9
이 기능에 대한 몇 가지 테스트를 수행했으며 EqualsBuilder가 그렇지 않은 경우에 대해 깊은 비교를 수행하는 것 같습니다.
Howard May

일시적인 필드를 무시하지 않는 방법이 있습니까?
핀치

@Pinch 나는 당신을 들었습니다. 심층 비교 도구 unitils는 눈에 띄는 영향 이 없을 때에도 변수를 비교하기 때문에 정확하게 결함이 있다고 말할 수 있습니다 . 변수 비교의 또 다른 (바람직하지 않은) 결과는 순수 클로저 (자체 상태가 없음)가 지원되지 않는다는 것입니다. 또한 비교 객체가 동일한 런타임 유형이어야합니다. 나는 소매 를 감고 이러한 문제를 해결하는 심층 비교 도구의 나만 의 버전 을 만들었습니다 .
beluchin

@Wolfgang에 우리를 안내 할 샘플 코드가 있습니까? 그 인용구는 어디서 뽑았나요?
anon58192932

30

나는이 질문을 좋아한다! 주로 거의 대답하지 않았거나 나쁘게 대답하지 않았기 때문입니다. 아직 아무도 알아 내지 못한 것 같습니다. 버진 영토 :)

우선,도없는 생각 사용에 대한 equals. 의 계약 equalsjavadoc 내에서 정의는 동치 관계 (재귀, 대칭 및 전이)입니다 하지 동등 관계. 이를 위해서는 비대칭이어야합니다. 그것의 유일한 구현은 equals진정한 평등 관계입니다 java.lang.Object. equals그래프의 모든 것을 비교 하는 데 사용 했더라도 계약을 위반할 위험이 상당히 높습니다. Josh Bloch가 Effective Java 에서 지적했듯이 동등 계약은 매우 쉽게 깨뜨릴 수 있습니다.

"균등 계약을 유지하면서 인스턴스화 가능한 클래스를 확장하고 측면을 추가 할 수있는 방법은 없습니다."

어쨌든 부울 메서드가 실제로 무슨 소용이 있습니까? 원본과 복제품 사이의 모든 차이점을 실제로 캡슐화하는 것이 좋을 것 같지 않습니까? 또한 여기서는 그래프의 각 개체에 대한 비교 코드를 작성 / 유지하는 데 신경 쓰지 않고 시간이 지남에 따라 변경 될 때 소스에 따라 확장 될 무언가를 찾고 있다고 가정합니다.

정말 원하는 것은 일종의 상태 비교 도구입니다. 이 도구를 구현하는 방법은 실제로 도메인 모델의 특성과 성능 제한에 따라 다릅니다. 제 경험상 일반적인 마법 총알은 없습니다. 그리고 많은 반복에서 느려질 입니다. 그러나 복제 작업의 완전성을 테스트하기 위해서는 작업을 꽤 잘 수행 할 것입니다. 가장 좋은 두 가지 옵션은 직렬화와 반영입니다.

발생할 수있는 몇 가지 문제 :

  • 컬렉션 순서 : 두 컬렉션이 동일한 객체를 보유하지만 순서가 다른 경우 유사한 것으로 간주해야합니까?
  • 무시할 필드 : 일시적입니까? 공전?
  • 유형 동등성 : 필드 값이 정확히 동일한 유형이어야합니까? 아니면 하나가 다른 하나를 확장해도 괜찮습니까?
  • 더 있지만 잊어 버렸습니다 ...

XStream은 매우 빠르며 XMLUnit과 결합하면 몇 줄의 코드만으로 작업을 수행 할 수 있습니다. XMLUnit은 모든 차이점을보고하거나 처음 발견 한 부분에서 멈출 수 있기 때문에 좋습니다. 출력에는 다른 노드에 대한 xpath가 포함되어 있습니다. 기본적으로 정렬되지 않은 컬렉션을 허용하지 않지만 그렇게하도록 구성 할 수 있습니다. 특별한 차이 핸들러 (라고 함 DifferenceListener)를 삽입 하면 순서 무시를 포함하여 차이를 처리하려는 방식을 지정할 수 있습니다. 그러나 가장 단순한 사용자 지정 이상의 작업을 수행하려는 즉시 작성하기가 어려워지고 세부 정보가 특정 도메인 개체에 묶여있는 경향이 있습니다.

개인적으로 선호하는 것은 리플렉션을 사용하여 선언 된 모든 필드를 순환하고 각 필드를 드릴 다운하여 이동하면서 차이점을 추적하는 것입니다. 경고 : 스택 오버플로 예외가 마음에 들지 않는 한 재귀를 사용하지 마십시오. 스택을 사용하여 범위 내에 유지 (사용LinkedList또는 뭔가). 나는 일반적으로 과도 및 정적 필드를 무시하고 이미 비교 한 객체 쌍을 건너 뛰기 때문에 누군가가 자기 참조 코드를 작성하기로 결정한 경우 무한 루프로 끝나지 않습니다 (그러나 나는 항상 기본 래퍼를 비교합니다. , 동일한 객체 참조가 종종 재사용되기 때문에). 컬렉션 순서를 무시하고 특수 유형 또는 필드를 무시하도록 미리 구성 할 수 있지만 주석을 통해 필드 자체에 대한 상태 비교 정책을 정의하고 싶습니다. IMHO는 런타임에 클래스에 대한 메타 데이터를 제공하기 위해 주석이 의미하는 바입니다. 다음과 같은 것 :


@StatePolicy(unordered=true, ignore=false, exactTypesOnly=true)
private List<StringyThing> _mylist;

나는 이것이 실제로 정말 어려운 문제라고 생각하지만 완전히 해결할 수 있습니다! 그리고 일단 당신에게 효과가있는 것이 있으면 정말, 정말, 편리합니다 :)

그럼 행운을 빕니다. 그리고 순수한 천재적인 것을 생각해 내면 공유하는 것을 잊지 마십시오!


15

java-util : https://github.com/jdereg/java-util 내의 DeepEquals 및 DeepHashCode ()를 참조하십시오.

이 클래스는 원래 작성자가 요청한 작업을 정확히 수행합니다.


4
경고 : DeepEquals는 객체가있는 경우 객체의 .equals () 메서드를 사용합니다. 이것은 당신이 원하는 것이 아닐 수도 있습니다.
Adam

4
equals () 메서드가 명시 적으로 추가 된 경우에만 클래스에서 .equals ()를 사용하고, 그렇지 않으면 멤버 별 비교를 수행합니다. 여기서 논리는 누군가가 사용자 정의 equals () 메소드를 작성하려고했다면이를 사용해야한다는 것입니다. 향후 향상 : 플래그가 존재하더라도 equals () 메서드를 무시하도록 허용합니다. java-util에는 CaseInsensitiveMap / Set과 같은 유용한 유틸리티가 있습니다.
John DeRegnaucourt 2014 년

필드 비교에 대해 걱정합니다. 필드의 차이는 객체의 클라이언트의 관점에서 관찰 할 수 없으며 여전히 필드를 기반으로 한 심층 비교는 플래그를 지정합니다. 또한 필드를 비교하려면 객체가 제한 될 수있는 동일한 런타임 유형이어야합니다.
beluchin

위의 @beluchin에 대답하기 위해 DeepEquals.deepEquals ()는 항상 필드 별 비교를 수행하지 않습니다. 첫째, 메서드에 .equals ()가있는 경우 (Object에있는 것이 아님) 사용하거나 무시할 수있는 옵션이 있습니다. 둘째, Maps / Collections를 비교할 때 Collection 또는 Map 유형이나 Collection / Map의 필드를 보지 않습니다. 대신 논리적으로 비교합니다. LinkedHashMap은 동일한 순서로 동일한 내용과 요소를 갖는 경우 TreeMap과 동일 할 수 있습니다. 순서가 지정되지 않은 컬렉션 및지도의 경우 크기와 동일 항목 만 필요합니다.
John DeRegnaucourt 2010 년

Maps / Collections를 비교할 때 Collection 또는 Map 유형이나 Collection / Map의 필드를 보지 않습니다. 대신, 논리적으로 @JohnDeRegnaucourt를 비교합니다. 즉, 컬렉션 / 맵에만 적용되는 것이 아니라 모든 유형에 적용해야하는 항목 만 비교하는 이 논리적 비교를 주장 public합니다.
beluchin

10

equals () 메서드 재정의

여기에 설명 된대로 EqualsBuilder.reflectionEquals () 를 사용하여 클래스 의 equals () 메서드를 간단히 재정의 할 수 있습니다 .

 public boolean equals(Object obj) {
   return EqualsBuilder.reflectionEquals(this, obj);
 }

7

Hibernate Envers에 의해 수정 된 두 엔티티 인스턴스의 비교를 구현하기 만하면됩니다. 나는 내 자신의 다른 글을 쓰기 시작했지만 다음 프레임 워크를 발견했습니다.

https://github.com/SQiShER/java-object-diff

동일한 유형의 두 개체를 비교할 수 있으며 변경, 추가 및 제거가 표시됩니다. 변경 사항이 없으면 객체가 동일합니다 (이론상). 검사 중에 무시해야하는 게터에 대한 주석이 제공됩니다. 프레임 작업은 동등성 검사보다 훨씬 더 광범위한 응용 프로그램을 가지고 있습니다. 즉, 변경 로그를 생성하는 데 사용하고 있습니다.

성능은 괜찮습니다. JPA 엔티티를 비교할 때 먼저 엔티티 관리자에서 분리해야합니다.


6

저는 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();
}

5
목록 이외의 컬렉션은 다른 순서로 요소를 반환 할 수 있으므로 문자열 비교가 실패합니다.
Alexey Berezkin 2014

또한 직렬화 가능하지 않은 클래스는 실패합니다
Zwelch

6

에서 AssertJ , 당신은 할 수 있습니다 :

Assertions.assertThat(expectedObject).isEqualToComparingFieldByFieldRecursively(actualObject);

아마도 모든 경우에 작동하지는 않지만 생각할 수있는 더 많은 경우에 작동 할 것입니다.

문서 내용은 다음과 같습니다.

테스트중인 개체 (실제)가 속성 / 필드 비교 (상속 된 항목 포함)에 의한 속성 / 필드의 재귀 적 비교를 기반으로 주어진 개체와 동일하다는 것을 확인합니다. 실제와 같음 구현이 적합하지 않은 경우 유용 할 수 있습니다. 재귀 속성 / 필드 비교는 사용자 정의 같음 구현이있는 필드에 적용되지 않습니다. 즉, 필드 별 비교 대신 재정의 된 같음 메서드가 사용됩니다.

재귀 비교는주기를 처리합니다. 기본적으로 부동 소수점은 1.0E-6의 정밀도와 비교되고 1.0E-15의 두 배가됩니다.

각각 usingComparatorForFields (Comparator, String ...) 및 usingComparatorForType (Comparator, Class)을 사용하여 (중첩 된) 필드 또는 유형별로 사용자 지정 비교기를 지정할 수 있습니다.

비교할 개체는 다른 유형일 수 있지만 동일한 속성 / 필드를 가져야합니다. 예를 들어 실제 객체에 이름 문자열 필드가있는 경우 다른 객체에도 하나가 있어야합니다. 개체에 이름이 같은 필드와 속성이있는 경우 속성 값이 필드 위에 사용됩니다.


1
isEqualToComparingFieldByFieldRecursively이제 더 이상 사용되지 않습니다. 사용 assertThat(expectedObject).usingRecursiveComparison().isEqualTo(actualObject);: 대신
dargmuesli

5

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);

2
등가에 영향을 미치지 않는 생성 된 클래스를 처리해야하는 경우 특히 유용합니다!
Matthias B

1
stackoverflow.com/a/1449051/829755가 이미 언급했습니다. 당신은 해당 게시물을 편집해야
user829755

1
@ user829755 이런 식으로 포인트를 잃습니다. 그래서 포인트 게임에 관한 모든 것)) 사람들은 일을 마치고 크레딧을받는 것을 좋아합니다. 나도 그렇습니다.
gavenkoa

3

Hamcrest에는 matcher samePropertyValuesAs가 있습니다. 그러나 JavaBeans Convention (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
    }
}

사용자 빈-게터 및 세터 포함

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;
    }

}

이것은 속성에 isFoo대한 읽기 메소드를 사용하는 POJO를 가질 때까지 훌륭하게 작동 Boolean합니다. 그것을 고치기 위해 2016 년부터 열린 PR이 있습니다. github.com/hamcrest/JavaHamcrest/pull/136
Snekse

2

객체가 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);
    }
}

1

Linked List 예제는 처리하기 어렵지 않습니다. 코드가 두 개체 그래프를 순회 할 때 방문한 개체를 Set 또는 Map에 배치합니다. 다른 개체 참조로 이동하기 전에이 집합은 개체가 이미 이동되었는지 확인하기 위해 테스트됩니다. 그렇다면 더 이상 갈 필요가 없습니다.

나는 LinkedList를 사용한다고 말한 사람에 동의합니다 (스택과 같지만 동기화 된 메서드가 없으므로 더 빠릅니다). 반사를 사용하여 각 필드를 가져 오는 동안 스택을 사용하여 개체 그래프를 탐색하는 것이 이상적인 솔루션입니다. 한 번 작성하면이 "외부"equals () 및 "external"hashCode ()는 모든 equals () 및 hashCode () 메소드가 호출해야하는 것입니다. 다시는 고객 equals () 메소드가 필요하지 않습니다.

Google 코드에 나열된 전체 개체 그래프를 가로 지르는 약간의 코드를 작성했습니다. json-io (http://code.google.com/p/json-io/)를 참조하세요. Java 개체 그래프를 JSON으로 직렬화하고 그로부터 역 직렬화합니다. 공용 생성자, 직렬화 가능 여부, 직렬화 불가능 등 모든 Java 객체를 처리합니다.이 동일한 순회 코드는 외부 "equals ()"및 외부 "hashcode ()"구현의 기반이됩니다. Btw, JsonReader / JsonWriter (json-io)는 일반적으로 기본 제공 ObjectInputStream / ObjectOutputStream보다 빠릅니다.

이 JsonReader / JsonWriter는 비교에 사용할 수 있지만 해시 코드에는 도움이되지 않습니다. 범용 hashcode () 및 equals ()를 원하면 자체 코드가 필요합니다. 일반적인 그래프 방문자와 함께이 문제를 해결할 수 있습니다. 우리는 볼 것이다.

기타 고려 사항-정적 필드-간단합니다. 정적 필드가 모든 인스턴스에서 공유되므로 모든 equals () 인스턴스가 정적 필드에 대해 동일한 값을 가지므로 건너 뛸 수 있습니다.

임시 필드의 경우 선택 가능한 옵션입니다. 때로는 과도 상태가 다른 시간을 계산하지 않기를 원할 수 있습니다. "때로는 너트처럼 느껴지지만 때로는 그렇지 않습니다."

json-io 프로젝트 (다른 ​​프로젝트의 경우)로 다시 확인하면 외부 equals () / hashcode () 프로젝트를 찾을 수 있습니다. 아직 이름은 없지만 분명 할 것입니다.


1

아파치는 당신에게 무언가를 제공하고 두 객체를 문자열로 변환하고 문자열을 비교하지만 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--;
        }
    }
}}

0

나는 당신이 이것을 알고 있다고 생각하지만 이론적으로, 당신은 항상 두 객체가 진정으로 동일하다고 주장하기 위해 .equals를 재정의해야합니다. 이는 멤버에서 재정의 된 .equals 메서드를 확인 함을 의미합니다.

이런 종류의 이유는 .equals가 Object에 정의 된 이유입니다.

이것이 지속적으로 이루어 졌다면 문제가 없을 것입니다.


2
문제는 내가 작성하지 않은 대규모 기존 코드베이스에 대해 테스트를 자동화하고 싶다는 것입니다 ... :)
Uri

0

그러한 깊은 비교를위한 중단 보장이 문제가 될 수 있습니다. 다음은 무엇을해야합니까? (이러한 비교기를 구현하면 좋은 단위 테스트가 될 것입니다.)

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));

새로운 질문이 있으면 질문하기 버튼 을 클릭하여 질문하십시오 . 컨텍스트를 제공하는 데 도움이되는 경우이 질문에 대한 링크를 포함하십시오.
YoungHobbit

@younghobbit : 아니 이것은 새로운 질문이 아닙니다. 답변의 물음표는 해당 플래그를 적절하게 만들지 않습니다. 더 많은 관심을 기울이십시오.
Ben Voigt 2015 년

이것에서 : Using an answer instead of a comment to get a longer limit and better formatting.이것이 주석이라면 왜 답변 섹션을 사용합니까? 그것이 내가 그것을 신고 한 이유입니다. 때문에 ?. 이 답변은 댓글을 남기지 않은 다른 사람이 이미 신고했습니다. 방금 리뷰 대기열에 있습니다. 내가 더 조심 했어야했다는 것이 내 잘못 일 수 있습니다.
YoungHobbit 2015-10-17

0

Ray Hulha 솔루션에서 영감을 얻은 가장 쉬운 솔루션 은 객체를 직렬화 한 다음 원시 결과를 자세히 비교하는 것입니다.

직렬화는 byte, 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());
    }
}   

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.