Apache Commons equals / hashCode 빌더 [닫기]


155

내가 아는 궁금 사용하여 여기에 대해 어떤 사람을 생각 org.apache.commons.lang.builder EqualsBuilder/ HashCodeBuilder 이행을위한 equals/를 hashCode? 직접 작성하는 것보다 더 나은 방법일까요? Hibernate와 잘 작동합니까? 당신의 의견은 무엇입니까?


16
reflectionEqualsand reflectionHashcode함수에 유혹을받지 마십시오 . 성능은 절대적인 킬러입니다.
skaffman

14
나는 어제 동등한 것에 대해 여기에서 약간의 토론을 보았고 약간의 자유 시간을 가졌기 때문에 나는 빠른 테스트를했다. 다른 등가 구현을 가진 4 개의 객체가 있습니다. 이클립스 생성, equalsbuilder.append, equalsbuilder.reflection 및 pojomatic 주석. 기준은 일식이었다. equalsbuilder.append는 3.7x가 걸렸습니다. pojomatic은 5 배가 걸렸습니다. 리플렉션 기반은 25.8 배가 걸렸습니다. 나는 반사 기반의 단순함을 좋아하고 "pojomatic"이라는 이름을 견딜 수 없기 때문에 매우 낙담했습니다.
digitaljoel

5
또 다른 옵션은 Project Lombok입니다. 리플렉션보다는 바이트 코드 생성을 사용하므로 Eclipse 생성뿐만 아니라 성능도 우수해야합니다. projectlombok.org/features/EqualsAndHashCode.html
Miles

답변:


212

commons / lang 빌더는 훌륭하며 몇 년 동안 눈에 띄는 성능 오버 헤드 (최대 절전 모드 유무에 관계없이)를 사용해 왔습니다. 그러나 Alain이 쓴 것처럼 구아바 방식은 훨씬 좋습니다.

다음은 샘플 Bean입니다.

public class Bean{

    private String name;
    private int length;
    private List<Bean> children;

}

Commons / Lang으로 구현 된 equals () 및 hashCode ()는 다음과 같습니다.

@Override
public int hashCode(){
    return new HashCodeBuilder()
        .append(name)
        .append(length)
        .append(children)
        .toHashCode();
}

@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return new EqualsBuilder()
            .append(name, other.name)
            .append(length, other.length)
            .append(children, other.children)
            .isEquals();
    } else{
        return false;
    }
}

Java 7 이상 (구아바에서 영감을 얻음)과 함께 여기 :

@Override
public int hashCode(){
    return Objects.hash(name, length, children);
}

@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return Objects.equals(name, other.name)
            && length == other.length // special handling for primitives
            && Objects.equals(children, other.children);
    } else{
        return false;
    }
}

참고 :이 코드는 원래 구아바를 참조했지만 주석에서 지적 했듯이이 기능은 JDK에 도입되었으므로 더 이상 구아바가 필요하지 않습니다.

보시다시피 Guava / JDK 버전은 더 짧고 불필요한 도우미 객체를 피합니다. 같으면 이전 Object.equals()호출이 false를 반환 하면 평가를 단락 할 수 있습니다 (공평하게 : commons / lang에는 위와 같이 단락을 허용 ObjectUtils.equals(obj1, obj2)하는 대신 사용할 수있는 동일한 의미를 갖는 방법이 있습니다 EqualsBuilder).

그렇습니다. 공통 랭 빌더는 수동으로 구성 equals()하고 hashCode()메소드 (또는 Eclipse가 생성 할 끔찍한 괴물) 보다 매우 바람직 하지만 Java 7 + / Guava 버전이 훨씬 좋습니다.

그리고 최대 절전 모드에 대한 참고 사항 :

equals (), hashCode () 및 toString () 구현에서 게으른 컬렉션 사용에주의하십시오. 열린 세션이 없으면 비참하게 실패합니다.


참고 (about equals ()) :

a) 위의 equals () 버전 모두에서 다음 단축키 중 하나 또는 둘 다를 사용할 수 있습니다.

@Override
public boolean equals(final Object obj){
    if(obj == this) return true;  // test for reference equality
    if(obj == null) return false; // test for null
    // continue as above

b) equals () 계약에 대한 해석에 따라 라인을 변경할 수도 있습니다

    if(obj instanceof Bean){

    // make sure you run a null check before this
    if(obj.getClass() == getClass()){ 

두 번째 버전을 사용하는 경우 메소드 super(equals())내부 에서 호출하려고 할 수도 있습니다 equals(). 여기에 의견이 다르 므로이 질문에서 주제에 대해 설명합니다.

수퍼 클래스를 Guava Objects.hashcode () 구현에 통합하는 올바른 방법은 무엇입니까?

(그것에 대해 비록 hashCode(), 동일 적용 equals())


참고 ( kayahr의 의견에서 영감을 얻음 )

Objects.hashCode(..)Arrays.hashCode(...)기본 필드가 많은 경우 ( 기본과 같이 ) 성능이 저하 될 수 있습니다. 이러한 경우 EqualsBuilder실제로 더 나은 솔루션 일 수 있습니다.


34
Java 7 Objects.equals에서도 마찬가지입니다. download.oracle.com/javase/7/docs/api/java/util/…
Thomas Jung

3
내가 올바르게 읽고 있다면 Josh Bloch는 Effective Java , 항목 8에서 equals () 메소드에서 getClass ()를 사용해서는 안된다고 말합니다 . 오히려 instanceof를 사용해야합니다.
Jeff Olson

6
@SeanPatrickFloyd Guava-way는 varargs에 대한 배열 객체를 생성 할뿐만 아니라 모든 매개 변수를 객체로 변환합니다. 따라서 10 개의 int 값을 전달하면 10 개의 Integer 객체와 배열 객체가 생깁니다. commons-lang 솔루션은 해시 코드에 몇 개의 값을 추가하든 단일 개체 만 만듭니다. 와 같은 문제입니다 equals. 구아바는 모든 값을 객체로 변환하고 commons-lang은 단 하나의 새로운 객체 만 만듭니다.
kayahr

1
@wonhee 나는 이것이 더 낫다는 것에 동의하지 않는다. 리플렉션을 사용하여 해시 코드를 계산하는 것은 내가 할 일이 아닙니다. 성능 오버 헤드는 무시해도되지만 잘못된 느낌 일뿐입니다.
숀 패트릭 플로이드 11

1
@kaushik 클래스 최종 실제로 두 버전의 잠재적 인 문제를 해결하고 (instanceof를하고 getClass ()) 한 경우에만 잎 클래스에 등호 ()를 구현할
숀 패트릭 플로이드에게

18

여러분, 일어나요! Java 7부터 표준 라이브러리에는 equalshashCode 에 대한 도우미 메소드가 있습니다. 그들의 사용법은 구아바 방법의 사용법과 완전히 같습니다.


a)이 질문을 받았을 때 Java 7은 아직 없었습니다. b) 기술적으로는 동등하지 않습니다. jdk에는 Objects.equals 메소드와 Guava의 Objects.equal 메소드가 있습니다. Guava 버전에서만 정적 가져 오기를 사용할 수 있습니다. 그것은 단지 화장품 일 뿐이지 만 구아바 이외의 지역이 눈에 띄게 더 복잡해집니다.
Sean Patrick Floyd

Objects.equals가 인스턴스의 .equals 메서드를 호출하기 때문에 객체 equals 메서드를 재정의하는 좋은 방법은 아닙니다. 인스턴스의 .equals 메서드 내에서 Objects.equals를 호출하면 스택 오버플로가 발생합니다.
dardo

루프에 빠질 때 예를 들어 줄 수 있습니까?
미하일 Golubtsov

OP는 개체 내에서 equals () 메서드를 재정의하도록 요청하고 있습니다. 정적 메서드 Objects.equals ()의 문서에 따르면 : "인수가 서로 같으면 true를 반환하고 그렇지 않으면 false를 반환합니다. 따라서 두 인수가 모두 null이면 true가 반환되고 정확히 하나의 인수가 null이면 false는 돌려 보냈다. 그렇지 않으면, 평등이 (가) 첫 번째 인수의 방법과 동일 사용에 의해 결정된다. "따라서, 당신은 그것을 호출 할 것 () 재정의 된 인스턴스 같음 내 Objects.equals ()를 사용하면 자신의 equals 메소드의 다음 Objects.equals () 그런 다음 다시 스택 오버플로를 발생시킵니다.
dardo

우리는 구조적인 평등을 구현에 대해 말하는 @dardo, 그래서 두 물체가있는 경우 서로 동일한 의미 필드 않습니다. 위의 구아바 예제를 참조하십시오.
Mikhail Golubtsov 2016 년

8

타사 라이브러리에 의존하지 않고 (자원이 제한된 장치를 실행 중일 수도 있음) 자신 만의 방법을 입력하고 싶지 않은 경우 IDE를 사용하여 작업을 수행 할 수도 있습니다 (예 : 일식 사용)

Source -> Generate hashCode() and equals()...

원하는대로 구성 할 있고 변경 사항 지원 해야하는 '기본'코드 제공됩니다.


예 (일부 Juno) :

import java.util.Arrays;
import java.util.List;

public class FooBar {

    public String string;
    public List<String> stringList;
    public String[] stringArray;

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((string == null) ? 0 : string.hashCode());
        result = prime * result + Arrays.hashCode(stringArray);
        result = prime * result
                + ((stringList == null) ? 0 : stringList.hashCode());
        return result;
    }
    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        FooBar other = (FooBar) obj;
        if (string == null) {
            if (other.string != null)
                return false;
        } else if (!string.equals(other.string))
            return false;
        if (!Arrays.equals(stringArray, other.stringArray))
            return false;
        if (stringList == null) {
            if (other.stringList != null)
                return false;
        } else if (!stringList.equals(other.stringList))
            return false;
        return true;
    }

}

14
그러나 Eclipse에서 생성 한 코드는 읽을 수없고 유지 관리 할 수 ​​없습니다.
숀 패트릭 플로이드

6
일식에서와 같이 끔찍한 것을 생각하지 마십시오 equals. 타사 라이브러리에 의존하지 않으려면 Objects.equal자신 과 같은 한 줄 방법을 작성 하십시오. 한두 번만 사용하더라도 코드가 더 나아집니다!
maaartinus 님

@maaartinus equals/ hashCode한 줄 방법 ​​???
FrVaBe

1
@maaartinus Guava는 타사 라이브러리입니다. 타사 라이브러리를 사용하여 AVOID를 원한다면 내 솔루션을 사용할 수 있다고 지적했습니다.
FrVaBe

1
@FrVaBe : 그리고 저는 "타사 라이브러리에 의존하고 싶지 않다면 Objects.equal과 같은 한 줄 방법을 작성하십시오."라고 썼습니다. 그런 다음 구아바를 사용하여 AVOID에 사용할 수있는 한 줄짜리 방법을 작성했지만 길이는 약 절반으로 줄었습니다.
maaartinus

6

EqualsBuilder 및 HashCodeBuilder에는 수동으로 작성된 코드와 다른 두 가지 주요 측면이 있습니다.

  • null 처리
  • 인스턴스 생성

EqualsBuilder 및 HashCodeBuilder를 사용하면 null 일 수있는 필드를 쉽게 비교할 수 있습니다. 수동으로 작성된 코드를 사용하면 많은 상용구가 생성됩니다.

반면 EqualsBuilder는 equals 메소드 호출 당 인스턴스를 작성합니다. equals 메소드가 자주 호출되면 많은 인스턴스가 작성됩니다.

최대 절전 모드의 경우 equals 및 hashCode 구현은 차이가 없습니다. 그것들은 단지 구현 세부 사항입니다. 최대 절전 모드로로드 된 거의 모든 도메인 오브젝트의 경우, 빌더의 런타임 오버 헤드 (이스케이프 분석이없는 경우도)는 무시할 수 있습니다 . 데이터베이스 및 통신 오버 헤드가 상당합니다.

skaffman이 언급했듯이 리플렉션 버전은 프로덕션 코드에서 사용할 수 없습니다. 성찰이 느려지고 "구현"이 가장 단순한 수업 이외의 모든 사람에게는 정확하지 않을 것입니다. 새로 소개 된 회원이 평등 방식을 변경함에 따라 모든 회원을 고려하는 것도 위험합니다. 리플렉션 버전은 테스트 코드에서 유용 할 수 있습니다.


나는 리플렉션 구현이 "가장 간단한 클래스를 제외하고는 정확하지 않을 것"이라고 동의하지 않는다. 빌더를 사용하면 원하는 경우 필드를 명시 적으로 제외 할 수 있으므로 구현은 실제로 비즈니스 키 정의에 따라 다릅니다. 불행히도 리플렉션 기반 구현의 성능 측면에 동의하지 않습니다.
digitaljoel

1
@digitaljoel 예, 필드를 제외 할 수 있지만 이러한 정의는 리팩토링 저장이 아닙니다. 그래서 나는 의도적으로 언급하지 않았습니다.
Thomas Jung


0

id가 기본 키인 엔티티 bean을 처리하는 경우 단순화 할 수 있습니다.

   @Override
   public boolean equals(Object other)
   {
      if (this == other) { return true; }
      if ((other == null) || (other.getClass() != this.getClass())) { return false; }

      EntityBean castOther = (EntityBean) other;
      return new EqualsBuilder().append(this.getId(), castOther.getId()).isEquals();
   }

0

제 생각에는 Hibernate, 특히 일부 엔티티의 길이, 이름 및 자녀를 비교하는 답변의 예와 잘 어울리지 않습니다. Hibernate 는 비즈니스 키 를 사용하여 equals () 및 hashCode ()에서 사용하도록 권장 하며 그 이유가 있습니다. 비즈니스 키에서 auto equals () 및 hashCode () 생성기를 사용하는 경우 괜찮습니다. 앞에서 언급 한 것처럼 성능 문제 만 고려하면됩니다. 그러나 사람들은 일반적으로 IMO가 매우 잘못된 모든 속성을 사용합니다. 예를 들어 현재 @AutoProperty와 함께 Pojomatic을 사용하여 엔터티를 작성하는 프로젝트를 진행하고 있습니다.

hashCode () 및 equals ()를 사용하는 두 가지 주요 시나리오는 다음과 같습니다.

  • 영구 클래스의 인스턴스를 세트에 넣을 때 (다중 값 연관을 나타내는 권장 방법) 및
  • 분리 된 인스턴스의 재 연결을 사용하는 경우

엔티티가 다음과 같다고 가정 해 봅시다.

class Entity {
  protected Long id;
  protected String someProp;
  public Entity(Long id, String someProp);
}

Entity entity1 = new Entity(1, "a");
Entity entity2 = new Entity(1, "b");

둘 다 어느 시점에서 어떤 세션에서 가져온 Hibernate와 동일한 엔티티입니다 (그들의 id와 클래스 / 테이블은 동일합니다). 그러나 모든 소품에 auto equals () 해시 코드 ()를 구현하면 무엇을 가질 수 있습니까?

  1. entity2가 entity1이 이미 존재하는 영구 세트에 놓으면 두 번 배치되고 커미트 중에 예외가 발생합니다.
  2. 분리 된 entity2를 session1에 연결하려는 경우 entity1이 이미 존재하는 경우 (아마도 특별히 테스트하지는 않았 음) 제대로 병합되지 않습니다.

따라서 99 %의 프로젝트를 위해 Hibernate 개념과 일치하는 기본 엔터티 클래스에서 한 번 작성된 equals () 및 hashCode () 구현을 사용합니다.

@Override
public boolean equals(Object obj) {
    if (StringUtils.isEmpty(id))
        return super.equals(obj);

    return getClass().isInstance(obj) && id.equals(((IDomain) obj).getId());
}

@Override
public int hashCode() {
    return StringUtils.isEmpty(id)
        ? super.hashCode()
        : String.format("%s/%s", getClass().getSimpleName(), getId()).hashCode();
}

과도 엔티티의 경우 지속 단계에서 Hibernate가 수행하는 것과 동일한 작업을 수행합니다. 인스턴스 일치를 사용합니다. 영속 객체의 경우 고유 키, 즉 테이블 / ID를 비교합니다 (복합 키는 절대 사용하지 않습니다).


0

다른 사람들이 유용하다고 생각하는 경우 위에서 언급 한 추가 객체 생성 오버 헤드를 피하는 해시 코드 계산을 위해이 도우미 클래스를 생각해 냈습니다. 실제로 Objects.hash () 메서드의 오버 헤드는 각 레벨마다 새로운 배열을 만들 것입니다!).

사용 예 :

public int hashCode() {
    return HashCode.hash(HashCode.hash(timestampMillis), name, dateOfBirth); // timestampMillis is long
}

public int hashCode() {
    return HashCode.hash(super.hashCode(), occupation, children);
}

HashCode 도우미 :

public class HashCode {

    public static int hash(Object o1, Object o2) {
        return add(Objects.hashCode(o1), o2);
    }

    public static int hash(Object o1, Object o2, Object o3) {
        return hash(Objects.hashCode(o1), o2, o3);
    }

    ...

    public static int hash(Object o1, Object o2, ..., Object o10) {
        return hash(Objects.hashCode(o1), o2, o3, ..., o10);
    }

    public static int hash(int initial, Object o1, Object o2) {
        return add(add(initial, o1), o2);
    }

    ...

    public static int hash(int initial, Object o1, Object o2, ... Object o10) {
        return add(... add(add(add(initial, o1), o2), o3) ..., o10);
    }

    public static int hash(long value) {
        return (int) (value ^ (value >>> 32));
    }

    public static int hash(int initial, long value) {
        return add(initial, hash(value));
    }

    private static int add(int accumulator, Object o) {
        return 31 * accumulator + Objects.hashCode(o);
    }
}

나는 10이 도메인 모델에서 최대의 합리적인 속성 수라는 것을 알았습니다. 더 많은 것이 있으면 문자열과 프리미티브 힙을 유지하는 대신 리팩토링하고 더 많은 클래스를 도입해야한다고 생각합니다.

단점은 다음과 같습니다. 주로 기본 요소 및 / 또는 배열을 깊이 해시해야하는 경우 유용하지 않습니다. (일반적으로 제어 할 수없는 평평한 (전송) 객체를 처리해야 할 경우)

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