web-dev-qa-db-ja.com

equals()の代わりにComparatorを使用して2つのJavaコレクションを比較する

問題文

比較したい同じタイプのオブジェクトのコレクションが2つあります。この場合、オブジェクトのequals()を考慮しない属性に基づいてそれらを比較したいと思います。私の例では、たとえば、ランク付けされた名前のコレクションを使用しています。

_public class Name {
    private String name;
    private int weightedRank;

    //getters & setters

    @Override
    public boolean equals(Object obj) {
        return this.name.equals(obj.name); //Naive implementation just to show
                                           //equals is based on the name field.
    }
}
_

2つのコレクションを比較して、各コレクションの位置iについて、その位置の各名前のweightedRankが同じ値であることを表明したいと思います。グーグルを実行しましたが、Commons Collectionsやその他のAPIで適切なメソッドが見つからなかったため、次のことを思いつきました。

_public <T> boolean comparatorEquals(Collection<T> col1, Collection<T> col2,
        Comparator<T> c)
{
    if (col1 == null)
        return col2 == null;
    if (col2 == null) 
        return false;

    if (col1.size() != col2.size())
        return false;

    Iterator<T> i1 = col1.iterator(), i2 = col2.iterator();

    while(i1.hasNext() && i2.hasNext()) {
        if (c.compare(i1.next(), i2.next()) != 0) {
            return false;
        }
    }

    return true;
}
_

質問

これを行う別の方法はありますか? CommonsCollectionsの明らかな方法を見逃しましたか?

関連

私も見つけました この質問 on SOこれは似ていますが、その場合はequals()をオーバーライドする方が少し理にかなっていると思います。

編集

これに非常に似たものが、近い将来(この記事の執筆時点で) Apache Commons Collections のリリースに入る予定です。 https://issues.Apache.org/jira/browse/COLLECTIONS-446 を参照してください。

18
Matt Lachman

この方法が実際に優れているかどうかはわかりませんが、「別の方法」です...

元の2つのコレクションを取得し、各ベースオブジェクトのアダプターを含む新しいコレクションを作成します。アダプターには、.equals()および.hashCode()Name.calculateWeightedRank()に基づくものとして実装されている必要があります。次に、通常のコレクションの等式を使用して、アダプターのコレクションを比較できます。

*編集*

AdapterにEclipseの標準hashCode/equals生成を使用します。コードは、各基本コレクションでadaptCollectionを呼び出し、次に2つの結果をList.equals()で呼び出します。

public class Adapter {

    public List<Adapter> adaptCollection(List<Name> names) {
        List<Adapter> adapters = new ArrayList<Adapter>(names.size());

        for (Name name : names) {
            adapters.add(new Adapter(name));
        }

        return adapters;
    }


    private final int name;

    public Adapter(Name name) {
        this.name = name.getWeightedResult();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + name;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Adapter other = (Adapter) obj;
        if (name != other.name)
            return false;
        return true;
    }

}
1
sharakan

「比較」と「同等性」の概念を切り離すために、Guava Equivalent クラスを使用できます。 Comparatorの代わりにEquivalentサブクラスを受け入れる比較メソッド(AFAIK Guavaにはありません)を作成する必要がありますが、少なくともコードの混乱は少なく、同等の基準に基づいてコレクションを比較できます。

等価ラップされたオブジェクトのコレクションを使用すると( 等価のwrapメソッド を参照)、sharakanによって提案されたアダプターベースのソリューションに似ていますが、等価実装はアダプター実装から分離され、次のことが可能になります。複数の等価基準を簡単に使用できます。

6
lbalazscs

バージョン4以降にisEqualCollectionに追加された新しいCollectionUtilsメソッドを使用できます。このメソッドは、Equatorインターフェース実装によって提供される外部比較メカニズムを使用します。次のjavadocsを確認してください: CollectionUtils.isEqualCollection(...) および Equator

4
nndru

[〜#〜] edit [〜#〜]:古い回答を削除しました。

もう1つのオプションは、次のようなWeightedというインターフェイスを作成することです。

public interface Weighted {
    int getWeightedRank();
}

次に、Nameクラスにこのインターフェイスを実装してもらいます。次に、メソッドを次のように変更できます。

 public <T extends Weighted> boolean weightedEquals(Collection<T> col1, Collection<T> col2)
{
    if (col1 == null)
      return col2 == null;
     if (col2 == null) 
      return false;

  if (col1.size() != col2.size())
      return false;

  Iterator<T> i1 = col1.iterator(), i2 = col2.iterator();

  while(i1.hasNext() && i2.hasNext()) {
      if (i1.next().getWeightedRank() != i2.next().getWeightedRank()) {
          return false;
      }
  }

  return true;
}

次に、重み付けして比較する必要のある追加のクラスを見つけたら、それらをコレクションに入れて、相互に比較することもできます。ただのアイデア。

0
nattyddubbs