web-dev-qa-db-ja.com

Java Comparatorのようなものを作ることは可能ですか?カスタムequals()とhashCode()を実装するためのものです

オブジェクトの配列があり、同じIDを持つオブジェクトを除いて、オブジェクトの別の配列と連結したい。そのオブジェクトはシステムの多くの場所で使用されており、ハッシュコードや等号が実装されていません。ですから、hashCode()equals()を実装したくありません。そのオブジェクトが使用されているシステムのどこかで何かを壊してしまうのではないかと思います。

そのすべてのオブジェクトをセットに入れたいのですが、どういうわけかオブジェクトにカスタムhashCode()equals()を使用させます。カスタムComparatorに似ていますが、同等です。

52
dhblah

はい、そのようなことは可能です。ただし、オブジェクトをHashMapやHashSetなどに入れることはできません。これは、標準のコレクションクラスが、キーオブジェクトがequalsおよびhashCodeメソッドを提供することを期待しているためです。 (それは彼らが機能するように設計されている方法です...)

代替案:

  1. 実際のクラスのインスタンスを保持するラッパークラスを実装し、equalsおよびhashCodeの独自の実装を提供します。

  2. 「ハッシュ可能」オブジェクトを使用して等価およびハッシュコード機能を提供できる独自のハッシュテーブルベースのクラスを実装します。

  3. 箇条書きをかじって、関連するクラスにequalsおよびhashCodeオーバーライドを実装してください。

実際、コードベースがこれらのオブジェクトが等しいという意味の一貫した概念を使用するneedsである可能性が最も高いため、3番目のオプションがおそらく最良です。他にも、コードの見直しが必要であることを示唆するものがあります。たとえば、セットと思われるものを表すために、現在、Set実装の代わりにオブジェクトの配列を使用しているという事実。

一方、現在の実装には、実際の(または想像上の)パフォーマンス上の理由があったか、またはあります。例えばメモリ使用量の削減。その場合は、配列として表される2つのセットを連結するなどの操作を実行するためのヘルパーメソッドを多数作成する必要があります。

34
Stephen C

ユーザーが同値関係を求めている場合の90%は、すでにより簡単な解決策です。 IDのみに基づいて多数の重複を排除したいですか?それらすべてをIDをキーとしてMapに入れて、そのvalues()コレクションを取得できますか?

13

HashingStrategy は、探しているコンセプトです。これは、equalsとhashcodeのカスタム実装を定義できる戦略インターフェイスです。

_public interface HashingStrategy<E>
{
    int computeHashCode(E object);
    boolean equals(E object1, E object2);
}
_

他の人が指摘したように、組み込みのHashingStrategyまたはHashSetHashMapを使用することはできません。 Eclipseコレクション には、UnifiedSetWithHashingStrategyというセットとUnifiedMapWithHashingStrategyというマップが含まれています。

例を見てみましょう。以下は、Dataで使用できる単純なUnifiedSetWithHashingStrategyクラスです。

_public class Data
{
    private final int id;

    public Data(int id)
    {
        this.id = id;
    }

    public int getId()
    {
        return id;
    }

    // No equals or hashcode
}
_

UnifiedSetWithHashingStrategyを設定して使用する方法は次のとおりです。

_Java.util.Set<Data> set =
  new UnifiedSetWithHashingStrategy<>(HashingStrategies.fromFunction(Data::getId));
Assert.assertTrue(set.add(new Data(1)));

// contains returns true even without hashcode and equals
Assert.assertTrue(set.contains(new Data(1)));

// Second call to add() doesn't do anything and returns false
Assert.assertFalse(set.add(new Data(1)));
_

Mapを使用しないのはなぜですか? UnifiedSetWithHashingStrategyUnifiedMapの半分のメモリを使用し、HashMapのメモリの4分の1を使用します。また、便利なキーがなく、タプルのような合成キーを作成する必要がある場合もあります。それはより多くのメモリを浪費する可能性があります。

ルックアップをどのように実行しますか?セットにはcontains()がありますが、get()はありません。 UnifiedSetWithHashingStrategyPoolに加えてMutableSetを実装するため、get()の形式も実装します。

注:私はEclipseコレクションのコミッターです。

10
Craig P. Motlin

もちろん、等価比較とHashCodeを提供するいくつかの外部オブジェクトを作成できます。ただし、Javaの組み込みコレクションは、そのようなオブジェクトを比較/ルックアップに使用しません。

私はかつてパッケージコレクションで次のようなインターフェイスを作成しました(英語に翻訳したばかりです)。

public interface HashableEquivalenceRelation {

    /**
     * Returns true if two objects are considered equal.
     *
     * This should form an equivalence relation, meaning it
     * should fulfill these properties:
     *  <ul>
     *    <li>Reflexivity:  {@code areEqual(o, o)}
     *            should always return true.</li>
     *    <li>Symmetry: {@code areEqual(o1,o2) == areEqual(o2,o1)}
     *            for all objects o1 and o2</li>
     *    <li>Transitivity: If {@code areEqual(o1, o2)} and {@code areEqual(o2,o3)},
     *            then {@code areEqual(o1,o3}} should hold too.</li>
     *  </ul>
     * Additionally, the relation should be temporary consistent, i.e. the
     * result of this method for the same two objects should not change as
     * long as the objects do not change significantly (the precise meaning of
     * <em>change significantly</em> is dependent on the implementation).
     *
     * Also, if {@code areEqual(o1, o2)} holds true, then {@code hashCode(o1) == hashCode(o2)}
     * must be true too.
     */
    public boolean areEqual(Object o1, Object o2);

    /**
     * Returns a hashCode for an arbitrary object.
     *
     * This should be temporary consistent, i.e. the result for the same
     * objects should not change as long as the object does not change significantly
     * (with change significantly having the same meaning as for {@link areEqual}).
     *
     * Also, if {@code areEqual(o1, o2)} holds true, then {@code hashCode(o1) == hashCode(o2)}
     * must be true too.
     */
    public int hashCode(Object o);

}

CustomCollectionCustomSetCustomListCustomMapなどのインターフェイスのグループをJava.utilのインターフェイスのように定義しましたが、 Object.equalsによって提供される組み込みの関係ではなく、すべてのメソッドに対するこのような等価関係。デフォルトの実装もいくつかありました:

/**
 * The equivalence relation induced by Object#equals.
 */
public final static EquivalenceRelation DEFAULT =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2)
        {
            return
                o1 == o2 ||
                o1 != null &&
                o1.equals(o2);
        }
        public int hashCode(Object ob)
        {
            return
                ob == null?
                0 :
                ob.hashCode();
        }
        public String toString() { return "<DEFAULT>"; }
    };

/**
 * The equivalence relation induced by {@code ==}.
 * (The hashCode used is {@link System#identityHashCode}.)
 */
public final static EquivalenceRelation IDENTITY =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2) { return o1 == o2; }
        public int hashCode(Object ob) { return System.identityHashCode(ob); }
        public String toString() { return "<IDENTITY>"; }
    };

/**
 * The all-relation: every object is equivalent to every other one.
 */
public final static EquivalenceRelation ALL =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2) { return true; }
        public int hashCode(Object ob) { return 0; }
        public String toString() { return "<ALL>"; }
    };

/**
 * An equivalence relation partitioning the references
 * in two groups: the null reference and any other reference.
 */
public final static EquivalenceRelation NULL_OR_NOT_NULL =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2)
        {
            return (o1 == null && o2 == null) ||
                (o1 != null && o2 != null);
        }
        public int hashCode(Object o) { return o == null ? 0 : 1; }
        public String toString() { return "<NULL_OR_NOT_NULL>"; }
    };

/**
 * Two objects are equivalent if they are of the same (actual) class.
 */
public final static EquivalenceRelation SAME_CLASS =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2)
        {
            return o1 == o2 || o1 != null && o2 != null &&
                o1.getClass() == o2.getClass();
        }
        public int hashCode(Object o) { return o == null ? 0 : o.getClass().hashCode(); }
        public String toString() { return "<SAME_CLASS>"; }
    };


/**
 * Compares strings ignoring case.
 * Other objects give a {@link ClassCastException}.
 */
public final static EquivalenceRelation STRINGS_IGNORE_CASE =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2)
        {
            return o1 == null ?
                o2 == null :
                ((String)o1).equalsIgnoreCase((String)o2);
        }
        public int hashCode(Object o)
        {
            return o == null ? -12345 : ((String)o).toUpperCase().hashCode();
        }
        public String toString() { return "<STRINGS_IGNORE_CASE>"; }
    };


/**
 * Compares {@link CharSequence} implementations by content.
 * Other object give a {@link ClassCastException}.
 */
public final static EquivalenceRelation CHAR_SEQUENCE_CONTENT =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2) 
        {
            CharSequence seq1 = (CharSequence)o1;
            CharSequence seq2 = (CharSequence)o2;
            if (seq1 == null ^ seq2 == null) // nur eins von beiden null
                return false;
            if (seq1 == seq2)   // umfasst auch den Fall null == null
                return true;
            int size = seq1.length();
            if (seq2.length() != size)
                return false;
            for (int i = 0; i < size; i++)
                {
                    if (seq1.charAt(i) != seq2.charAt(i))
                        return false;
                }
            return true;
        }
        /**
         * Entrspricht String.hashCode
         */
        public int hashCode(Object o)
        {
            CharSequence sequence = (CharSequence)o;
            if (sequence == null)
                return 0;
            int hash = 0;
            int size = sequence.length();
            for (int i = 0; i < size; i++)
                {
                    hash = hash * 31 + sequence.charAt(i);
                }
            return hash;
        }
    };
4
Paŭlo Ebermann

TreeSet を使用すると、ここで役立ちますか? TreeSetは実際に、compare/compareToを使用して順序付けとSetベースの動作を実行し、カスタムコンパレーターを定義してi nコンストラクターの1つ を使用できるようにします。

1
whaley

ちょうどこの問題があり、簡単な解決策を考え出しました。それがどれほどメモリを消費するかわかりません。人々はすぐにそれを洗練することができると確信しています。

Comparatorが0を返す場合、要素は一致します。

_public static <E> Set<E> filterSet(Set<E> set, Comparator<E> comparator){
    Set<E> output = new HashSet<E>();
    for(E eIn : set){
        boolean add = true;
        for(E eOut : output){
            if(comparator.compare(eIn, eOut) == 0){
                add = false;
                break;
            }
        }
        if(add) output.add(eIn);
    }
    return output;
}
_

私の使用例は、同じドキュメントを指すURLの場合のように、重複するURLを除外する必要があったことです。 URLオブジェクトには、フラグメント以外のすべてが同じ場合にtrueを返すsamePage()メソッドがあります。

_filtered = Misc.filterSet(filtered, (a, b) -> a.sameFile(b) ? 0 : 1);
_
1
ndm13

コンパレータを使用して重複除外連結を行うことはできません。おそらくあなたはこのようなことをしたいと思っています:

List<Object> list = new ArrayList<Object>();
list.addAll( a );
list.addAll( b );
Collections.sort( list, new MyCustomComparator() );

問題は、コンパレータが等しい/等しくないだけでなく、相対的な順序も比較する必要があることです。等しくないオブジェクトxとyが与えられた場合、一方が他方よりも大きい場合は答える必要があります。実際にオブジェクトを比較しようとしているわけではないので、それはできません。一貫した答えが得られない場合は、並べ替えアルゴリズムを無限ループに送ります。

私はあなたのための解決策を持っています。 Javaには、LinkedHashSetと呼ばれるクラスがあります。その利点は、重複を挿入できないが挿入順序は維持されます。コンパレータを実装するのではなく、実際のオブジェクトを保持するラッパークラスを実装します。そしてhashCode/equalsを実装します。