33

リフレクションを使用したオブジェクトの同等性には、apache HashCodeBuilderとEqualsBuilderをよく使用しますが、最近、同僚から、エンティティに多くのプロパティが含まれている場合、リフレクションを使用するとパフォーマンスが大幅に低下する可能性があると言われました。私が間違った実装を使用しているのではないかと心配していますが、私の質問は、次のアプローチのどれを好みますか?なぜ?

public class Admin {

    private Long id;
    private String userName;

    public String getUserName() {
        return userName;
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Admin)) {
            return false;
        }
        Admin otherAdmin  = (Admin) o;
        EqualsBuilder builder = new EqualsBuilder();
        builder.append(getUserName(), otherAdmin.getUserName());
        return builder.isEquals();
    }

    @Override
    public int hashCode() {
        HashCodeBuilder builder = new HashCodeBuilder();
        builder.append(getUserName());
        return builder.hashCode();
    }
}

public class Admin {

    private Long id;
    private String userName;

    public String getUserName() {
        return userName;
    }

    @Override
    public boolean equals(Object o) {
      return EqualsBuilder.reflectionEquals(this, o, Arrays.asList(id));
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this, Arrays.asList(id));
    }
}
4

6 に答える 6

22

もちろん、2番目のオプションはよりエレガントでシンプルです。ただし、パフォーマンスが心配な場合は、最初のアプローチを選択する必要があります。セキュリティマネージャが実行されている場合、2番目の方法も失敗します。私があなたの状況にあったら、私は最初の選択肢に行きます。

また、hashCodeを生成する最初のアプローチに誤りがあります。これは、 の代わりに使用する
必要があります。後者は、ハッシュコードビルダーオブジェクトのハッシュコードを返します。builder.toHashCode()builder.hashCode()

于 2012-06-06T11:39:58.370 に答える
17

2番目のオプションの方が魅力的ですが(コードが1行しかないため)、最初のオプションを選択します。

その理由は単にパフォーマンスです。小さなテストを実行した後、私はそれらの間に非常に大きな時間差を見つけました。

時間の概念を理解するために、次の2つの簡単なクラスを作成しました。

package equalsbuildertest;

import java.math.BigDecimal;
import java.util.Date;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class Class1 {

    private int field1;

    private boolean field2;

    private BigDecimal field3;

    private String field4;

    private Date field5;

    private long field6;

    public Class1(int field1, boolean field2, BigDecimal field3, String field4,
            Date field5, long field6) {
        super();
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
        this.field4 = field4;
        this.field5 = field5;
        this.field6 = field6;
    }

    public Class1() {
        super();
    }

    public int getField1() {
        return field1;
    }

    public void setField1(int field1) {
        this.field1 = field1;
    }

    public boolean isField2() {
        return field2;
    }

    public void setField2(boolean field2) {
        this.field2 = field2;
    }

    public BigDecimal getField3() {
        return field3;
    }

    public void setField3(BigDecimal field3) {
        this.field3 = field3;
    }

    public String getField4() {
        return field4;
    }

    public void setField4(String field4) {
        this.field4 = field4;
    }

    public Date getField5() {
        return field5;
    }

    public void setField5(Date field5) {
        this.field5 = field5;
    }

    public long getField6() {
        return field6;
    }

    public void setField6(long field6) {
        this.field6 = field6;
    }

    @Override
    public boolean equals(Object o) {
      return EqualsBuilder.reflectionEquals(this, o);
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }

}

と:

package equalsbuildertest;

import java.math.BigDecimal;
import java.util.Date;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class Class2 {

    private int field1;

    private boolean field2;

    private BigDecimal field3;

    private String field4;

    private Date field5;

    private long field6;

    public Class2(int field1, boolean field2, BigDecimal field3, String field4,
            Date field5, long field6) {
        super();
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
        this.field4 = field4;
        this.field5 = field5;
        this.field6 = field6;
    }

    public Class2() {
        super();
    }

    public int getField1() {
        return field1;
    }

    public void setField1(int field1) {
        this.field1 = field1;
    }

    public boolean isField2() {
        return field2;
    }

    public void setField2(boolean field2) {
        this.field2 = field2;
    }

    public BigDecimal getField3() {
        return field3;
    }

    public void setField3(BigDecimal field3) {
        this.field3 = field3;
    }

    public String getField4() {
        return field4;
    }

    public void setField4(String field4) {
        this.field4 = field4;
    }

    public Date getField5() {
        return field5;
    }

    public void setField5(Date field5) {
        this.field5 = field5;
    }

    public long getField6() {
        return field6;
    }

    public void setField6(long field6) {
        this.field6 = field6;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Class2)) {
            return false;
        }
        Class2 other = (Class2) obj;
        EqualsBuilder builder = new EqualsBuilder();
        builder.append(field1, other.field1);
        builder.append(field2, other.field2);
        builder.append(field3, other.field3);
        builder.append(field4, other.field4);
        builder.append(field5, other.field5);
        builder.append(field6, other.field6);
        return builder.isEquals();
    }

    @Override
    public int hashCode() {
        HashCodeBuilder builder = new HashCodeBuilder();
        builder.append(getField1());
        builder.append(isField2());
        builder.append(getField3());
        builder.append(getField4());
        builder.append(getField5());
        builder.append(getField6());
        return builder.hashCode();

    };

}

その2番目のクラスは、最初のクラスとほとんど同じですが、equalsとhashCodeが異なります。

その後、次のテストを作成しました。

package equalsbuildertest;

import static org.junit.Assert.*;

import java.math.BigDecimal;
import java.util.Date;

import org.junit.Test;

public class EqualsBuilderTest {

    @Test
    public void test1() {
        Class1 class1a = new Class1(1, true, new BigDecimal(0), "String", new Date(), 1L);
        Class1 class1b = new Class1(1, true, new BigDecimal(0), "String", new Date(), 1L);
        for (int i = 0; i < 1000000; i++) {
            assertEquals(class1a, class1b);
        }
    }

    @Test
    public void test2() {
        Class2 class2a = new Class2(1, true, new BigDecimal(0), "String", new Date(), 1L);
        Class2 class2b = new Class2(1, true, new BigDecimal(0), "String", new Date(), 1L);
        for (int i = 0; i < 1000000; i++) {
            assertEquals(class2a, class2b);
        }
    }

}

テストは非常に単純で、時間を測定するためだけに役立ちます。

結果は次のとおりです。

  • test1(2,024秒)
  • test2(0,039秒)

最高の時間を過ごすために、私はそれらを完全に等しくなるように選びました。NotEquals条件でテストを実行することを選択した場合、時間は短くなりますが、非常に大きな時間差も維持されます。

このテストは、Fedora21とEclipseLunaを搭載した64ビットIntelCore i5-3317U CPU @1.70GHzx4で実行します。

結論として、テンプレートを使用してとにかく入力できない可能性のある数行のコードを保存するために、パフォーマンスの大きな違いを危険にさらすことはありません(EclipseのWindows->設定はJava->エディター->テンプレートにあります)このような:

${:import(org.apache.commons.lang3.builder.HashCodeBuilder, org.apache.commons.lang3.builder.EqualsBuilder)}
@Override
public int hashCode() {
    HashCodeBuilder hashCodeBuilder = new HashCodeBuilder();
    hashCodeBuilder.append(${field1:field});
    hashCodeBuilder.append(${field2:field});
    hashCodeBuilder.append(${field3:field});
    hashCodeBuilder.append(${field4:field});
    hashCodeBuilder.append(${field5:field});
    return hashCodeBuilder.toHashCode();
}

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (getClass() != obj.getClass()) {
        return false;
    }
    ${enclosing_type} rhs = (${enclosing_type}) obj;
    EqualsBuilder equalsBuilder = new EqualsBuilder();
    equalsBuilder.append(${field1}, rhs.${field1});
    equalsBuilder.append(${field2}, rhs.${field2});
    equalsBuilder.append(${field3}, rhs.${field3});
    equalsBuilder.append(${field4}, rhs.${field4});
    equalsBuilder.append(${field5}, rhs.${field5});${cursor}
    return equalsBuilder.isEquals();
}
于 2015-04-07T20:03:42.670 に答える
11

私は2つの理由で2番目のオプションを好みます:

  1. 明らかに読みやすいです

  2. 関連するメトリックが含まれていない限り、最初のオプションのパフォーマンス引数は購入しません。たとえば、リフレクションベースの「等しい」は、一般的なエンドツーエンドのリクエストレイテンシに何ミリ秒追加されますか?全体として、それは何%の増加でしょうか?最適化が時期尚早である可能性が高いことを知らずに

于 2013-11-26T18:02:15.500 に答える
3

書かれた質問は、2番目のアプローチの利点の1つを明確に示しています。

最初のケースでは、正しいのではなく、間違いを犯すのは非常に簡単です。その結果、微妙なエラーが発生し、追跡が非常に困難になる可能性があります。return builder.hashCode()return builder.toHashCode()

2番目のケースでは、このタイプミスの可能性が排除され、バグを見つけようとしてキーボードに頭をぶつけることが少なくなります。

于 2014-11-28T02:51:35.007 に答える
3

これらのどちらも適切な実装ではないと思います。EqualsBuilderは、次の理由で使用するのに適したフレームワークではないと主張します。

  1. 拡張できません。同等性を主張しようとしているフィールドの1つが、ヌルとブランクを同等として扱う必要がある場合はどうなりますか?
  2. 変数のリストは、ハードコードされた変数であるかのように維持する必要があります。つまり、比較するすべての変数をリストする必要があります。この時点で、a == o.getA()&& b == o.getB()..。
  3. ご指摘のとおり、リフレクションを使用すると、何十億ものオブジェクトを粉砕するエンタープライズアプリケーションで追加のリソースが必要になります。このリフレクションを行うことは、メモリリークが発生するのと同じくらい悪いことです。

Apacheよりも優れたフレームワークが必要だと思います。

于 2015-02-16T00:51:19.380 に答える
2

@ Churchに同意しますが、ApacheHashCodeBuilderとEqualsBuilderは適切に実装されていません。HashCodeBuilderはまだ素数で遊んでいます!さらに、それは非常に多くの不必要な仕事をします。ソースを読んだことがありますか?

Since Java 5 (if not earlier), AbstractHashMap<> has not used modulo of a prime number to locate the hash bucket. Instead, the number of buckets is a power of two and the low order N bits of the hash code is used to locate the bucket.

Further, it will "mix" the hash code supplied by the application so that the bits are spread evenly and, thus, the buckets are filled evenly.

Thus, the correct way to override int Object.hashCode() is by returning the simplest, constant value with the highest arity in the population of objects that will cohabitate in any collection using the class.

Typically, the ID value unmodified is your best bet. If your ID field is integral, just cast it to (int) and return it. If it is a String or other Object, just return its hash code. You get the idea. For a compound identifier, return the field (or hashCode thereof) with the most distinct values. Less is more.

Of course, the contract between hashCode() and equals() must be met. Thus, equals() should be implemented accordingly. hashCode() need not use the full qualifiers needed for equality, but any fields used in hashCode() must be used in equals(). Here, methods like StringUtils.equals( s1, s2 ) are useful for handling the null values consistently and safely.

于 2015-07-16T15:10:26.500 に答える