web-dev-qa-db-ja.com

ArrayListにJavaのオブジェクトが含まれているかどうかを確認する最も効率的な方法

JavaのオブジェクトのArrayListがあります。オブジェクトには4つのフィールドがあり、そのうち2つを使用して、オブジェクトを別のものと見なします。配列にそのオブジェクトが含まれているかどうかを確認するために、これらの2つのフィールドを指定して、最も効率的な方法を探しています。

レンチは、これらのクラスがXSDオブジェクトに基づいて生成されるため、クラス自体を変更して.equalsを上書きすることはできません。

ループして、各オブジェクトの2つのフィールドを手動で比較し、見つかったときにブレークするよりも良い方法はありますか?それはとても厄介なようで、より良い方法を探しています。

編集: ArrayListは、オブジェクトに非整列化されたSOAP応答から取得されます。

71
Parrots

ソートとバイナリ検索のために、Javaの組み込みメソッドを備えたコンパレータを使用できます。次のようなクラスがあり、aとbがソートに使用するフィールドであるとします:

class Thing { String a, b, c, d; }

コンパレータを定義します:

Comparator<Thing> comparator = new Comparator<Thing>() {
  public int compare(Thing o1, Thing o2) {
    if (o1.a.equals(o2.a)) {
      return o1.b.compareTo(o2.b);
    }
    return o1.a.compareTo(o2.a);
  }
};

次に、リストをソートします。

Collections.sort(list, comparator);

最後に、バイナリ検索を実行します。

int i = Collections.binarySearch(list, thingToFind, comparator);
37
Fabian Steeg

制約を考えると、ブルートフォース検索(または検索が繰り返される場合はインデックスの作成)に固執しています。 ArrayListがどのように生成されるかについて詳しく説明してもらえますか。

きれいなコードだけを探している場合は、Apache Commons Collectionsクラス、特に CollectionUtils.find() を既製の構文糖に使用することを検討してください。

ArrayList haystack = // ...
final Object needleField1 = // ...
final Object needleField2 = // ...

Object found = CollectionUtils.find(haystack, new Predicate() {
   public boolean evaluate(Object input) {
      return needleField1.equals(input.field1) && 
             needleField2.equals(input.field2);
   }
});
10

リストが sorted の場合、 binary search を使用できます。そうでない場合、より良い方法はありません。

これを頻繁に行っている場合、最初にリストをソートすることはほぼ間違いなく価値があります。クラスを変更することはできないため、ソートと検索を行うには Comparator を使用する必要があります。

6
Michael Myers

あなたが私の ForEach DSL のユーザーであれば、Detectクエリでそれを行うことができます。

Foo foo = ...
Detect<Foo> query = Detect.from(list);
for (Detect<Foo> each: query) 
    each.yield = each.element.a == foo.a && each.element.b == foo.b;
return query.result();
4
akuhn

Equalsメソッドwereでこれら2つのフィールドを比較したとしても、論理的には、手動で行うのと同じコードになります。 OK

4
oxbow_lakes

各オブジェクトの2つのフィールドをループして手動で比較し、見つかったときにブレークするよりも良い方法はありますか?それはとても厄介なようで、より良い方法を探しています。

あなたの懸念が保守性である場合、あなたは Fabian Steeg 提案することができます(それは私がすることです)が、おそらく「最も効率的」ではありません(最初に配列をソートしてからバイナリ検索)が、確かに最もクリーンで優れたオプション。

本当に効率性に関心がある場合は、オブジェクトのフィールドをハッシュとして使用し、HashMapをストレージとして使用するカスタムリスト実装を作成できます。しかし、おそらくこれは多すぎるでしょう。

次に、データを入力する場所をArrayListからYourCustomListに変更する必要があります。

好む:

 List list = new ArrayList();

 fillFromSoap( list );

に:

 List list = new MyCustomSpecialList();

 fillFromSoap( list );

実装は次のようになります。

class MyCustomSpecialList extends AbstractList  { 
    private Map<Integer, YourObject> internalMap;

    public boolean add( YourObject o ) { 
         internalMap.put( o.getThatFieldYouKnow(), o );
    }

    public boolean contains( YourObject o ) { 
        return internalMap.containsKey( o.getThatFieldYouKnow() );
    }

}

HashSetとほとんど同じですが、ここでの問題はHashSetがhashCodeメソッドの適切な実装に依存していることです。代わりに、1つのオブジェクトを他のオブジェクトと等しくする「知っているフィールド」をハッシュとして使用します。

もちろん、上記のスニペットよりもはるかにトリッキーなリストを最初から実装するので、 Fabian Steeg の提案の方が実装がより簡単で簡単です(このようなものはより効率的です)

最後に何をしたか教えてください。

2
OscarRyz

たぶん、リストはあなたが必要なものではありません。

多分 TreeSet がより良いコンテナになるでしょう。 O(log N)の挿入と取得、および順序付けされた反復を取得します(ただし、重複は許可されません)。

LinkedHashMap は、ユースケースにさらに適している可能性がありますので、チェックしてください。

2
Ben Hardy

3つの基本オプションがあります。

1)検索パフォーマンスが最重要であり、それが実際的である場合、一度構築された(およびリストが変更された場合/変更された)ハッシュテーブルの形式を使用します。

2)リストが便利にソートされているか、ソートするのが実用的であり、O(log n)の検索で十分な場合は、ソートして検索します。

3)O(n)検索が十分に速い場合、またはデータ構造または代替を操作/維持するのが非現実的である場合は、リストを反復処理します。

リストを単純に反復するよりも複雑なコードを書く前に、いくつかの質問を検討する価値があります。

  • なぜ別のものが必要なのですか? (時間)パフォーマンス?優雅?保守性?再利用しますか?これらはすべて、別々または一緒に問題ない理由ですが、ソリューションに影響を与えます。

  • 問題のデータ構造をどの程度管理していますか?構築方法に影響を与えることはできますか?後で管理しますか?

  • データ構造(および基礎となるオブジェクト)のライフサイクルは何ですか?一度に構築され、変更されることはありませんか、または非常に動的ですか?コードでライフサイクルを監視(または変更)できますか?

  • メモリフットプリントなど、他の重要な制約はありますか?重複に関する情報は重要ですか?等。

1
Jeremy Rishel

キーとしてフィールド値に基づいてこれらのオブジェクトのHashMapを構築することは、パフォーマンスの観点から価値があります。 Mapsを一度入力し、非常に効率的にオブジェクトを見つける

1
Rocket Surgeon

同じリストで何度も検索する必要がある場合は、インデックスを作成すると効果があります。

繰り返し処理を行い、キーとして探している等しい値と値として適切なノードでHashMapを構築します。等しい値の誰かの代わりにすべてが必要な場合は、マップにリストの値タイプを持たせ、最初の反復でリスト全体を構築します。

インデックス作成のオーバーヘッドは、予想されるノードが見つかるまで移動するだけで影を落とす可能性があるため、これを行う前に測定する必要があることに注意してください。

最も簡単な解決策は、オブジェクトをラップし、ラップされたクラスのコレクションにcontains呼び出しを委任することです。これはコンパレータに似ていますが、結果のコレクションをソートすることを強制するものではなく、単にArrayList.contains()を使用できます。

public class Widget {
        private String name;
        private String desc;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }
    }



    public abstract class EqualsHashcodeEnforcer<T> {

        protected T wrapped;

        public T getWrappedObject() {
            return wrapped;
        }

        @Override
        public boolean equals(Object obj) {
            return equalsDelegate(obj);
        }

        @Override
        public int hashCode() {
            return hashCodeDelegate();
        }

        protected abstract boolean equalsDelegate(Object obj);

        protected abstract int hashCodeDelegate();
    }


    public class WrappedWidget extends EqualsHashcodeEnforcer<Widget> {

        @Override
        protected boolean equalsDelegate(Object obj) {
            if (obj == null) {
                return false;
            }
            if (obj == getWrappedObject()) {
                return true;
            }
            if (obj.getClass() != getWrappedObject().getClass()) {
                return false;
            }
            Widget rhs = (Widget) obj;

            return new EqualsBuilder().append(getWrappedObject().getName(),
                    rhs.getName()).append(getWrappedObject().getDesc(),
                    rhs.getDesc()).isEquals();
        }

        @Override
        protected int hashCodeDelegate() {

            return new HashCodeBuilder(121, 991).append(
                    getWrappedObject().getName()).append(
                    getWrappedObject().getDesc()).toHashCode();
        }

    }
0
jonathan.cone