IT

Apache Commons equals / hashCode 빌더

lottoking 2020. 6. 7. 10:09
반응형

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 처리
  • 인스턴스 생성

The EqualsBuilder and HashCodeBuilder make it easier to compare fields that could be null. With manually writen code this creates a lot of boilerplate.

The EqualsBuilder will on the other hand create an instance per equals method call. If your equals methods are call often this will create a lot of instances.

For Hibernate the equals and hashCode implementation make no difference. They are just an implementation detail. For almost all domain objects loaded with hibernate the runtime overhead (even without escape analysis) of the Builder can be ignored. Database and communication overhead will be significant.

As skaffman mentioned the reflection version cannot be used in production code. Reflection will be to slow and the "implementation" will not be correct for all but the simplest classes. Taking all members into account is also dangerous as newly introduced members change the equals method behaviour. The reflection version can be useful in test code.


If you don't to write your own, there is also the possibility to use google guava (formerly google collections)


If you are just dealing with the entity bean where id is a primary key, you can simplify.

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

In my opinion it doesn't play well with Hibernate, especially the examples from the answer comparing length, name and children for some entity. Hibernate advises to use business key to be used in equals() and hashCode(), and they have their reasons. If you use auto equals() and hashCode() generator on your business key, it's ok, just the performance problems need to be considered as mentioned previously. But people usually uses all properties what is IMO very wrong. For example I'm currently working on project where entities are written using Pojomatic with @AutoProperty, what I consider a really bad pattern.

Their two main scenarios to use hashCode() and equals() are:

  • when you put instances of persistent classes in a Set (the recommended way to represent many-valued associations) and
  • when you use reattachment of detached instances

So let's assume our entity looks like this:

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

Both are the same entity for Hibernate, which have been fetched from some session at some point (their id and class/table are equal). But when we implement auto equals() a hashCode() on all props, what do we have?

  1. When you put the entity2 to the persistent set where the entity1 already exists, this will be put twice and will result in exception during commit.
  2. If you want to attach the detached entity2 to the session, where entity1 already exists they (probably, I haven't tested this especially) won't be merged properly.

So, for 99% project I make, we use the following implementation of equals() and hashCode() written once in base entity class, which is consistent with the Hibernate concepts:

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

For the transient entity I do the same what Hibernate will do on persistence step, ie. I use the instance match. For the persistent objects I compare the unique key, which is the table/id (I never use composite keys).


Just in case, others will find it useful, I've come up with this Helper class for hash code computation that avoids extra object creation overhead mentioned above (in fact, the overhead of Objects.hash() method is even bigger when you have inheritance as it will create an new array on each level!).

Usage example:

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

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

The HashCode helper:

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

I've figured that 10 is the maximum reasonable number of properties in a domain model, if you have more you should think of refactoring and introducing more class instead of maintaining a heap of Strings and primitives.

The drawbacks are: it's not useful if you have mainly primitives and/or arrays that you need to hash deeply. (Normally this is the case when you have to deal with flat (transfer) objects that is out of your control).

참고URL : https://stackoverflow.com/questions/5038204/apache-commons-equals-hashcode-builder

반응형