11

TreeSetドメインロジックでソートされた要素が必要だとします。このロジックでは、等しくない要素の順序は関係ないため、compareメソッドは0を返すことができますが、この場合、それらをに入れることはできませんでしたTreeSet

だから、質問:私がこのようなコードからどのような不利な点があるか:

class Foo implements Comparable<Foo>{}
new TreeSet<Foo>(new Comparator<Foo>(){
    @Override
    public int compare(Foo o1, Foo o2) {
        int res = o1.compareTo(o2);
        if(res == 0 || !o1.equals(o2)){
            return o1.hashCode() - o2.hashCode();
        }
        return res;
    }
});

更新

Ok。@SPFloyd-seanizerや他の人が言ったように、それが常にメソッドとの間の一貫性である必要がある場合equals()。インターフェイスを削除してこのロジックを移動すると、より良い、またはさらに良いと思いますか(カプセル化を壊さずに実行できます)?したがって、次のようになります。hashcode()compareTo()ComparableComparator

class Foo{}
new TreeSet<Foo>(new Comparator<Foo>(){
    @Override
    public int compare(Foo o1, Foo o2) {
        //some logic start
        if(strictliBigger(o1, o2)){ return 1;}
        if(strictliBigger(o2, o1)){ return -1;}
        //some logic end
        if(res == 0 || !o1.equals(o2)){
            return o1.hashCode() - o2.hashCode();
        }
        return res;
    }
});

アップデート2

安定ソートが必要ない場合System.identityHashCode(x)よりも良いでしょうか?hashCode()

4

11 に答える 11

9

これは機能するかもしれませんが、ベストプラクティスにはほど遠いです。

SortedSetドキュメントから:

ソートされたセットがSetインターフェースを正しく実装するためには、ソートされたセットによって維持される順序(明示的なコンパレーターが提供されているかどうかに関係なく)はequalsと一致している必要があることに注意してください。(equalsとの整合性の正確な定義については、ComparableインターフェイスまたはComparatorインターフェイスを参照してください。)これは、Setインターフェイスがequals操作で定義されているが、並べ替えられたセットがcompareTo(またはcompare)メソッドを使用してすべての要素の比較を実行するためです。したがって、この方法で等しいと見なされる2つの要素は、ソートされたセットの観点からは等しいです。ソートされたセットの動作は、その順序がequalsと矛盾している場合でも明確に定義されています。Setインターフェースの一般的な契約に従わないだけです。

を実装するオブジェクトの場合、メソッドとのComparable間には常に一貫性が必要です。equals()hashcode()compareTo()


恐れ入りますが、 aSortedSetはあなたが望むものではなく、GuavaMultiSetも適切ではありません(複数の等しいアイテムを個別に取得できないため)。必要なのはSortedList。私が知っているような獣はありません(おそらくcommons-collectionsにありますが、それらは少しレガシー側にあります)。そこで、GuavaのForwardingListを基本クラスとして使用して実装しました。つまり、このリストはほとんどすべてをArrayList内部で使用するリストに委任しますが、そのメソッドで正しい挿入位置を見つけるために使用Collections.binarySearch()し、指定された位置に値を追加または設定するインターフェイスのすべてのオプションのメソッドにをスローadd()します。UnsupportedOperationExceptionListListIterator

コンストラクターはのコンストラクターと同じですArrayListが、それぞれにカスタムの2番目のバージョンもありますComparator。カスタムコンパレータを使用しない場合は、リスト要素を実装する必要があります。そうしないと、Comparable並べRuntimeException替え中にsが発生します。

public class SortedArrayList<E> extends ForwardingList<E> implements
    RandomAccess{

    private final class ListIteratorImpl extends ForwardingListIterator<E>{
        private final int start;
        public ListIteratorImpl(final int start){
            this.start = start;
        }

        @Override
        public void set(E element){throw new UnsupportedOperationException();}

        @Override
        public void add(E element){throw new UnsupportedOperationException();}

        @Override
        protected ListIterator<E> delegate(){return inner.listIterator(start);};

    }

    private Comparator<? super E> comparator;

    private List<E> inner;

    public SortedArrayList(){this(null, null, null);}

    @SuppressWarnings("unchecked")
    private SortedArrayList(
        final List<E> existing,
        final Collection<? extends E> values,
        final Comparator<? super E> comparator
    ){
        this.comparator =
            (Comparator<? super E>)
               (comparator == null
                   ? Ordering.natural()
                   : comparator   );
        inner = (
            existing == null
                ? (values == null
                      ? new ArrayList<E>(values)
                      : new ArrayList<E>()
                   )
                : existing;
    }

    public SortedArrayList(final Collection<? extends E> c){
        this(null, c, null);
    }

    public SortedArrayList(final Collection<? extends E> c,
        final Comparator<? super E> comparator){
        this(null, c, comparator);
    }

    public SortedArrayList(final Comparator<? super E> comparator){
        this(null, null, comparator);
    }

    public SortedArrayList(final int initialCapacity){
        this(new ArrayList<E>(initialCapacity), null, null);
    }

    public SortedArrayList(final int initialCapacity,
        final Comparator<? super E> comparator){
        this(new ArrayList<E>(initialCapacity), null, comparator);
    }

    @Override
    public boolean add(final E e){
        inner.add(
            Math.abs(
                Collections.binarySearch(inner, e, comparator)
            ) + 1,
            e
        );
        return true;
    }

    @Override
    public void add(int i, E e){throw new UnsupportedOperationException();}

    @Override
    public boolean addAll(final Collection<? extends E> collection){
        return standardAddAll(collection);
    }

    @Override
    public boolean addAll(int i,
        Collection<? extends E> es){
        throw new UnsupportedOperationException();
    }

    @Override
    protected List<E> delegate(){ return inner; }

    @Override
    public List<E> subList(final int fromIndex, final int toIndex){
        return new SortedArrayList<E>(
            inner.subList(fromIndex, toIndex),
            null,
            comparator
        );
    }

    @Override
    public ListIterator<E> listIterator(){ return new ListIteratorImpl(0); }

    @Override
    public ListIterator<E> listIterator(final int index){
        return new ListIteratorImpl(index);
    }

    @Override
    public E set(int i, E e){ throw new UnsupportedOperationException(); }

}
于 2010-12-03T09:52:30.070 に答える
2

hashcode()less thanメソッドはまたはを保証しませんgreater thancompare()同じ意味が得られるはずですが、equals()必須ではありません。

あなたの紛らわしいコードから私が理解できる限り(攻撃は意図されていません:))、あなたは重複をに追加したいと思いますTreeSet。そのため、この実装を思いついたのです。これが理由です、あなたはそれらをTreeSetドキュメントから引用して、に入れることができません、

セットの動作は、その順序がequalsと矛盾している場合でも明確に定義されています。Setインターフェースの一般的な契約に従わないだけです。

だから、あなたはyorメソッドで何かをする必要があるequals()ので、それは決して本当のことを返すことはできません。最適な実装は、

public boolean equals(Object o) {
    return false;
}

ちなみに、私の理解が正しければ、List代わりに使用して並べ替えてみませんか。

于 2010-12-03T09:51:40.490 に答える
2

注意してください:2つのFoosでもf1、あなたが得ることができます!つまり、メソッドで安定した並べ替えを行うことはできません。f2f1 != f2f1.hashCode() == f2.hashCode()compare

于 2010-12-03T09:58:32.083 に答える
2

Javaには、2つのオブジェクトのハッシュコードが等しくないという理由だけで異なる必要があるという規則はありません(そのため、あなたの場合o1.hashCode() - o2.hashCode()は戻る可能性が0あります)。

また、の動作はからの結果と一致しているequals() 必要compareTo()があります。これは必須ではありませんが、これを維持できない場合は、設計に大きな欠陥があることを示しています。

オブジェクトの他のフィールドを確認し、それらのいくつかを使用して比較を拡張し、!= 0オブジェクトの値を取得することを強くお勧めしますequals() == false

于 2010-12-03T09:59:34.537 に答える
1

非常に興味深い質問です。私が理解している限り、あなたの問題は重複した要素です。

o1.equals(o2)の場合、それらのハッシュコードも等しい可能性があると思います。これは、FooクラスでのhashCode()の実装に依存します。したがって、代わりにSystem.identityHashCode(x)を使用することをお勧めします。

于 2010-12-03T09:52:14.923 に答える
1

Foo同等のクラスがありますが、TreeSet<Foo>構造体で別の並べ替えを使用したいと考えています。次に、あなたのアイデアはそれを行う正しい方法です。そのコンストラクターを使用して、の自然ソートを「無効」にしFooます。

于 2010-12-03T09:54:55.217 に答える
1
int res = o1.compareTo(o2);

if(res == 0 || !o1.equals(o2)){
    return o1.hashCode() - o2.hashCode();
}

2つのオブジェクトが等しい場合(つまり、の場合res == 0)、これら2つのオブジェクトは同じハッシュコードを返すため、問題が発生する可能性があります。ハッシュコードはすべてのオブジェクトに固有ではありません。


@Stasを編集System.identityHashCode(Object x);してください、それでもあなたを助けません。その理由はjavadocに記載されています。

指定されたオブジェクトのクラスがオーバーライドするかどうかに関係なく、デフォルトのメソッドによって返されるのと同じハッシュコードを指定されたオブジェクトに対して返します。null参照のハッシュコードはゼロです。hashCode()hashCode()

于 2010-12-03T09:58:19.430 に答える
1

与えられた2つの要素に特定の予想される順序がないが、それでもそれらが等しくないと見なしたい場合は、とにかく指定された順序を返す必要があります。

他の人が投稿しているように、両方の要素の値は簡単に等しくなる可能性があるhashCode()ため、これは適切な候補ではありません。より良い選択かもしれませんが、それでも完全ではありません。一意の値も保証されないからです。hashCode()System.identityHashCode()identityHashCode()

Guava arbitrary()Orderingは、 Comparatorusingを実装しSystem.identityHashCode()ます。

于 2010-12-03T10:07:43.223 に答える
1

はい、他の人が上で言ったように、hashCode()はここで使用するのに安全ではありません。ただし、o1.compareTo(o2)== 0に関して等しいオブジェクトの順序を気にしない場合は、次のようにすることができます。

public int compare(Foo o1, Foo o2) {
        int res = o1.compareTo(o2);
        if (res == 0 && !o1.equals(o2)) {
            return -1;
        }
        return res;
}
于 2010-12-03T10:19:55.060 に答える
1

ここにはいくつかの問題があります。

  • ハッシュコードは一般的に一意ではなく、特にSystem.identityHashCode漠然と最新のJVMでは一意ではありません。

  • これは安定性の問題ではありません。配列を並べ替えていますが、ツリー構造を作成しています。ハッシュコードの衝突によりcompareゼロが返されます。これは、TreeSet一方のオブジェクトが優先され、もう一方のオブジェクトが破棄されることを意味します。リンクリストに劣化しません(手がかりの名前に「Set」が含まれています)。

  • 一般に、あるハッシュコードを別のハッシュコードから減算すると、整数のオーバーフローの問題が発生します。これは、比較が推移的ではない(つまり、壊れている)ことを意味します。運が良ければ、Sun / Oracleの実装では、System.identityHashCode常に正の値が返されます。これは、大規模なテストではおそらくこの特定の種類のバグが見つからないことを意味します。

を使用してこれを実現する良い方法はないと思いますTreeSet

于 2010-12-03T12:29:43.753 に答える
1

2つのポイントが関連している可能性があります。これらは、1つの状況での戻り値が-1として表示され、これは、関数パラメーター変数または関連する使用国で負の値が許可されているかどうか、および使用しているメソッドが許可されます。セレクターや選択ソートなどの標準的なデータ配置方法があり、紙の説明やコードは、コピーが職場にない場合は通常、国の当局から入手できます。大なり記号や小なり記号などの比較を使用すると、コードが高速化され、後のスクリプトまたはコードへの暗黙のドロップスルーによる同等性の直接比較の使用が回避されます。

于 2010-12-08T15:06:07.583 に答える