[java] Apache Commons equals / hashCode 빌더 [닫기]

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



답변

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실제로 더 나은 솔루션 일 수 있습니다.


답변

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


답변

타사 라이브러리에 의존하지 않고 (자원이 제한된 장치를 실행 중일 수도 있음) 자신 만의 방법을 입력하고 싶지 않은 경우 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;
    }

}


답변

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

  • null 처리
  • 인스턴스 생성

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

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

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

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


답변

직접 작성하지 않으면 Google 구아바 (이전의 Google 컬렉션) 를 사용할 수도 있습니다


답변

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


답변

제 생각에는 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를 비교합니다 (복합 키는 절대 사용하지 않습니다).