web-dev-qa-db-ja.com

Apache Commons equals / hashCodeビルダー

org.Apache.commons.lang.builderEqualsBuilder/HashCodeBuilderを使用してequals/hashCodeを実装することについて、ここの人々がどう思うか知りたいのですが。自分で書くよりも良い方法でしょうか? Hibernateでうまく機能しますか?あなたの意見は何ですか?

154
aug70co

Commons/langビルダーは素晴らしく、目立ったパフォーマンスオーバーヘッドなしで(休止状態の有無にかかわらず)何年も使用しています。しかし、Alainが書いているように、Guavaの方法はさらに優れています。

次にサンプル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;
    }
}

注:このコードはもともとGuavaを参照していましたが、コメントが指摘しているように、この機能はJDKで導入されたため、Guavaは不要になりました。

ご覧のとおり、Guava/JDKバージョンはより短く、余分なヘルパーオブジェクトを回避しています。等しい場合、以前のObject.equals()呼び出しがfalseを返す場合、評価を短絡することさえ可能にします(公平:commons/langには、代わりに使用できる同一のセマンティクスを持つObjectUtils.equals(obj1, obj2)メソッドがありますEqualsBuilderは上記のように短絡を許可します)。

そう:はい、commons langビルダーは、手動で構築されたequals()およびhashCode()メソッド(またはEclipseが生成するひどいモンスター)よりも非常に望ましいですが、Java 7+ /グアバのバージョンはさらに優れています。

また、Hibernateに関するメモ:

equals()、hashCode()、toString()の実装で遅延コレクションを使用する場合は注意してください。セッションを開いていない場合、それは惨めに失敗します。


注(約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()){ 

2番目のバージョンを使用する場合、おそらくsuper(equals())メソッド内でequals()を呼び出すこともできます。ここでは意見が異なります。トピックはこの質問で説明されています。

スーパークラスをGuava Objects.hashcode()実装に組み込む正しい方法?

hashCode()についてですが、equals()にも同じことが当てはまります)


注( kayahr )からのコメントに触発された

Objects.hashCode(..)(基礎となるArrays.hashCode(...)と同様)は、多くのプリミティブフィールドがある場合、パフォーマンスが低下する可能性があります。そのような場合、EqualsBuilderが実際にはより良いソリューションである可能性があります。

211

皆さん、起きて! Java 7から equals および hashCode のヘルパーメソッドがあります標準ライブラリ。それらの使用法は、グアバメソッドの使用法と完全に同等です。

16

サードパーティのライブラリに依存したくない場合(リソースが限られているデバイスを実行している場合があります)、独自のメソッドを入力したくない場合は、IDEを実行させることもできます仕事、例えばEclipseでの使用

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

can好きなように設定し、have to変更をサポートする 'ネイティブ'コードを取得します。


例(Eclipse 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;
    }

}
8
FrVaBe

EqualsBuilderとHashCodeBuilderには、手動で記述されたコードとは異なる2つの主な側面があります。

  • ヌル処理
  • インスタンス作成

EqualsBuilderおよびHashCodeBuilderを使用すると、nullの可能性があるフィールドを簡単に比較できます。手動で記述されたコードでは、これにより多くの定型文が作成されます。

一方、EqualsBuilderは、equalsメソッド呼び出しごとにインスタンスを作成します。 equalsメソッドが頻繁に呼び出されると、多くのインスタンスが作成されます。

Hibernateでは、equalsとhashCodeの実装に違いはありません。それらは実装の詳細にすぎません。 hibernateでロードされたほとんどすべてのドメインオブジェクトについて、Builderのランタイムオーバーヘッド(エスケープ分析がなくても)は無視できます。データベースと通信のオーバーヘッドが大きくなります。

スカッフマンが言及したように、リフレクション版は製品コードでは使用できません。リフレクションは遅くなり、「実装」は最も単純なクラスを除くすべてのクラスで正しくなくなります。すべてのメンバーを考慮することも、新しく導入されたメンバーがequalsメソッドの動作を変更するため、危険です。リフレクションバージョンは、テストコードで役立ちます。

6
Thomas Jung

独自に作成しない場合は、 google guava(以前のgoogle collections) を使用することもできます

4
Alain Pannetier

私の意見では、それはHibernate、特にいくつかのエンティティの長さ、名前、子供を比較する回答の例ではうまくいきません。 Hibernateは ビジネスキーを使用する をequals()およびhashCode()で使用することをお勧めしますが、それらには理由があります。ビジネスキーでauto equals()およびhashCode()ジェネレーターを使用する場合は問題ありません。前述のように、パフォーマンスの問題のみを考慮する必要があります。しかし、人々は通常、IMOが非常に間違っているすべてのプロパティを使用しています。例えば、私は現在、@ AutoPropertyでPojomaticを使用してエンティティを作成するプロジェクトに取り組んでいます。これは本当に悪いパターンだと考えています。

HashCode()とequals()を使用する2つの主なシナリオは次のとおりです。

  • setに永続クラスのインスタンスを配置する場合(多値の関連付けを表す推奨方法)
  • 分離されたインスタンスの再接続を使用する場合

エンティティが次のように見えると仮定しましょう。

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()hashCode()を実装すると、何ができますか?

  1. Entity1が既に存在する永続セットにentity2を配置すると、これは2回配置され、コミット中に例外が発生します。
  2. エンティティ1が既に存在するセッションにデタッチされたエンティティ2をアタッチする場合、それらは適切にマージされません(おそらく、これは特にテストしていません)。

そのため、私が作成する99%のプロジェクトでは、Hibernateの概念と一致する、ベースエンティティクラスで1回記述された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であると考えました。もっとある場合は、文字列とプリミティブのヒープを維持するのではなく、リファクタリングとクラスの追加を検討する必要があります。

欠点は次のとおりです:深くハッシュする必要がある主にプリミティブや配列がある場合、それは役に立ちません。 (通常、これは、制御できないフラット(転送)オブジェクトを処理する必要がある場合です)。

0
Vlad

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
DEREK LEE