Apache Commons equals / hashCodeビルダー[終了]


155

私はここの人々が使用して何を考え、知って興味 org.apache.commons.lang.builder EqualsBuilder/をHashCodeBuilder 実装するためのequals/をhashCode?自分で書くよりも良い習慣でしょうか?Hibernateでうまく機能しますか?あなたの意見は何ですか?


16
reflectionEqualsreflectionHashcode関数に誘惑されないでください。パフォーマンスは絶対的なキラーです。
skaffman '18

14
私は昨日、イコールについてここでいくつかの議論を見ました、そして、少しの自由時間があったので、私は簡単なテストをしました。等しい実装が異なる4つのオブジェクトがありました。eclipse生成、equalsbuilder.append、equalsbuilder.reflection、およびpojomaticアノテーション。ベースラインは日食でした。equalsbuilder.appendは3.7xかかりました。pojomaticは5倍かかりました。反射ベースは25.8倍かかりました。リフレクションベースのシンプルさが好きで、「pojomatic」という名前を我慢できないので、それはかなりがっかりしました。
digitaljoel

5
別のオプションはProject Lombokです。リフレクションではなくバイトコード生成を使用するため、Eclipseで生成されたものと同じように機能するはずです。 projectlombok.org/features/EqualsAndHashCode.html
マイル

回答:


212

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

以下はサンプル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以上(Guavaに触発された):

@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)。

だから:はい、コモンズの言語ビルダーは手動で構築equals()したhashCode()メソッドよりも非常に好ましいです(またはEclipseが生成する恐ろしいモンスター)、しかしJava 7+ / Guavaバージョンはさらに優れています。

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実際にはより良い解決策かもしれません。


34
:同じは、Java 7 Objects.equalsで可能になりますdownload.oracle.com/javase/7/docs/api/java/util/...
トーマス・ユング

3
私がそれを正しく読んでいる場合、Josh Blochは「Effective Java」の項目8で、equals()メソッドでgetClass()を使用しないでくださいと言っています。むしろ、instanceofを使用する必要があります。
ジェフオルソン

6
@SeanPatrickFloyd Guavaウェイは、varargsの配列オブジェクトを作成するだけでなく、すべてのパラメーターをオブジェクトに変換します。したがって、10個のint値を渡すと、10個のIntegerオブジェクトと配列オブジェクトになります。commons-langソリューションは、ハッシュコードに追加する値の数に関係なく、単一のオブジェクトのみを作成します。と同じ問題equals。Guavaはすべての値をオブジェクトに変換します。commons-langは新しいオブジェクトを1つだけ作成します。
カヤール、2013

1
@wonheeこれは良いことだと強く反対します。リフレクションを使用してハッシュコードを計算することは、私がこれまでやったことではありません。パフォーマンスのオーバーヘッドはおそらくごくわずかですが、間違っているように感じます。
Sean Patrick Floyd

1
@kaushikクラスをfinalにすることで、equals()をリーフクラスのみに実装する限り、両方のバージョン(instanceofおよびgetClass())の潜在的な問題が実際に解決されます
Sean Patrick Floyd

18

皆さん、目を覚ましてください!Java 7以降、標準ライブラリには、equalshashCodeのヘルパーメソッドあります。それらの使用法はGuavaメソッドの使用法と完全に同等です。


a)この質問がなされた時点で、Java 7はまだ存在していませんでした。b)技術的には、まったく同等ではありません。jdkには、GuavaのObjects.equalメソッドに対してObjects.equalsメソッドがあります。静的インポートはGuavaのバージョンでのみ使用できます。それは単なる化粧品だと私は知っていますが、それは非グアバを著しく乱雑にします。
Sean Patrick Floyd

Objects.equalsはインスタンスの.equalsメソッドを呼び出すため、これはオブジェクトのequalsメソッドをオーバーライドするのに適した方法ではありません。インスタンスの.equalsメソッド内でObjects.equalsを呼び出すと、スタックオーバーフローが発生します。
dardo

ループに陥ったときの例を挙げてください。
ミハイルゴルブツォフ2017

OPはObject内のequals()メソッドをオーバーライドすることを求めています。静的メソッドObjects.equals()のドキュメントのとおり、「引数が互いに等しい場合はtrueを返し、それ以外の場合はfalseを返します。そのため、両方の引数がnullの場合はtrueが返され、1つの引数がnullの場合はfalseが返されます。返される。そうでなければ、平等は最初の引数の等しい方法を用いて決定される。「だから、あなたはObjects.equalsを使用した場合()オーバーライドされたインスタンス内では、(等しい)それは(そしてObjects.equals、それ自身のequalsメソッドを呼びたいです)その後、再びそれ自体がスタックオーバーフローを引き起こします。
dardo

@dardo構造的等価性の実装について話しているので、2つのオブジェクトのフィールドが等しい場合、それらは互いに等しいことを意味します。上記のGuavaの例を参照してください。
ミハイルゴルブツォフ2017年

8

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

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

必要に応じ構成でき、変更時サポートする必要ある「ネイティブ」コードを取得します。


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

}

14
本当ですが、Eclipseによって生成されたコードは、読み取りも保守もできません。
Sean Patrick Floyd、

6
日食によって生成されたものほどひどいことを考えないでくださいequals。サードパーティのライブラリに依存したくない場合は、Objects.equal自分と同じように1行のメソッドを記述します。1回または2回だけ使用した場合でも、コードが大幅に改善されます。
maaartinus

@maaartinus equals/ hashCode一行メソッド???
FrVaBe 2012

1
@maaartinus Guavaはサードパーティのライブラリです。私のソリューションは、サードパーティのライブラリを使用したくない場合に使用できることを指摘しました。
FrVaBe 2012

1
@FrVaBe:そして、「サードパーティのライブラリに依存したくない場合は、Objects.equalのような1行のメソッドを自分で記述します。」そして、私はGuavaを使用してAVOIDを回避するために使用できる1行の方法を記述しましたが、それでも約半分の長さを半分にカットしました。
maaartinus 2012

6

EqualsBuilderとHashCodeBuilderには、手動で作成したコードとは異なる2つの主要な側面があります。

  • null処理
  • インスタンスの作成

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

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

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

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


リフレクションの実装が「最も単純なクラスを除いてすべてに正しくなるわけではない」ことに同意しません。Builderを使用すると、必要に応じてフィールドを明示的に除外できるため、実装は実際にビジネスキーの定義に依存します。残念ながら、リフレクションベースの実装のパフォーマンスの側面に同意することはできません。
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()を使用する2つの主なシナリオは次のとおりです。

  • 永続クラスのインスタンスをセットに入れるとき(多値の関連付けを表す推奨される方法)および
  • デタッチされたインスタンスの再接続を使用する場合

したがって、エンティティが次のようになっていると仮定します。

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. 切り離されたentity2をセッションに接続する場合、entity1がすでに存在している場合(おそらく、これは特にテストしていません)は正しくマージされません。

したがって、私が作成する99%のプロジェクトでは、ベースエンティティクラスで一度記述された次のequals()およびhashCode()の実装を使用します。これは、Hibernateの概念と一致しています。

@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が永続化ステップで行うのと同じことを行います。インスタンスマッチを使用します。永続オブジェクトについて、一意のキーであるtable / 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.