web-dev-qa-db-ja.com

セット内のオブジェクトの更新

私のアプリケーションにこのタイプがあるとしましょう:

public class A {
  public int id;
  public B b;

  public boolean equals(Object another) { return this.id == ((A)another).id; }
  public int hashCode() { return 31 * id; //Nice prime number }
}

およびSet <A>構造。これで、タイプAのオブジェクトがあり、次のことを実行したいと思います。

  • Aがセット内にある場合は、そのフィールドbをオブジェクトに一致するように更新します。
  • それ以外の場合は、セットに追加します。

したがって、そこにあるかどうかを確認するのは簡単で(contains)、セットに追加するのも簡単です。私の質問はこれです:内部のオブジェクトを更新するためのハンドルを取得するにはどうすればよいですか?インターフェイスSetにはgetメソッドがありません。私が考えることができる最善の方法は、セット内のオブジェクトを削除して、私のものを追加することでした。もう1つのさらに悪い方法は、イテレータを使用してセットをトラバースし、オブジェクトを見つけようとすることです。

私は喜んでより良い提案をします...これには他のデータ構造の効率的な使用が含まれます。

ユヴァル= 8-)

[〜#〜] edit [〜#〜]:回答ありがとうございます...残念ながら、ここで最良の回答を「受け入れる」ことはできません。Mapの使用を提案する回答は、この目的のために根本的にコレクションのタイプは少し極端になります(このコレクションはすでにHibernateを介してマップされています...)

17
Yuval

セットにはオブジェクトのインスタンスを1つしか含めることができないため(equalsメソッドとhashCodeメソッドで定義されているように)、それを削除してから追加するだけです。すでに存在する場合は、もう1つがセットから削除され、必要なものに置き換えられます。

私は似たようなことをするコードを持っています-私はオブジェクトをキャッシュしているので、特定のオブジェクトがGUIのさまざまな場所に表示され、常に同じものになります。その場合、セットを使用する代わりに、マップを使用していて、更新を取得します。新しいインスタンスを作成するのではなく、マップから取得して、その場で更新します。

19
Paul Tomblin

本当にMap<Integer,A>ではなくSet<A>を使用したいのです。

次に、IDを(A!にも格納されている場合でも)オブジェクトにマップします。したがって、新しいものを保存するのはこれです:

A a = ...;
Map<Integer,A> map = new HashMap<Integer,A>();
map.put( a.id, a );

完全な更新アルゴリズムは次のとおりです。

public static void update( Map<Integer,A> map, A obj ) {
  A existing = map.get( obj.id );
  if ( existing == null )
     map.put( obj.id, obj );
  else
     existing.b = obj.b;
}

ただし、もっと簡単かもしれません。Aのフィールドよりも多くのフィールドがあると思います。 そうでない場合Map<Integer,B>を使用するだけで実際に必要なものになり、何も表示されなくなります。

Map<Integer,B> map = new HashMap<Integer,B>();
// The insert-or-update is just this:
map.put( id, b );
11
Jason Cohen

セットを使用している場合は、削除/追加を使用するよりも簡単にすることはできないと思います。

_    set.remove(a);
    set.add(a);
_

一致するAが見つかった場合、それは削除され、新しいものを追加します。if (set.contains(A))条件も必要ありません。

IDと更新されたフィールドを持つオブジェクトがある場合andそのオブジェクトの他の側面についてはあまり気にしないので、それを捨てて置き換えてください。

そのIDに一致するAに対して他のことを行う必要がある場合は、セットを反復処理してそれを見つけるか、別のコンテナー(Jasonが提案したマップなど)を使用する必要があります。

6
18Rabbit

まだ誰もこれについて言及していませんが、可変プロパティに基づいてハッシュコードまたは同等のものを作成することは、あなたがすべきではない本当に本当に大きなことの1つです。コンストラクターを離れた後は、オブジェクトIDをいじくり回さないでください。そうすることで、将来的にバグを特定するのが非常に困難になる可能性が大幅に高まります。バグに見舞われなくても、常にequalsとに依存するすべてのデータ構造を適切に更新するように会計処理が行われます。一貫性のあるハッシュコードは、実行時にオブジェクトのIDを変更できるという認識されている利点をはるかに上回ります。

代わりに、コンストラクターを介してidを渡すことを強くお勧めします。変更する必要がある場合は、Aの新しいインスタンスを作成します。これにより、オブジェクトのユーザー(自分自身を含む)がコレクションクラス(および多くのその他)equalsおよびハッシュコードの不変の動作に依存します。

5
Kevin Day

Map<A,A>はどうですか?冗長であることはわかっていますが、希望する動作が得られると思います。本当に私はSetにget(Object o)メソッドがあるのを見たいです。

1
zds

ASetというデコレータを生成し、内部マップをバッキングデータ構造として使用することをお勧めします。

class ASet {
 private Map<Integer, A> map;
 public ASet() {
  map = new HashMap<Integer, A>();
 }

 public A updateOrAdd(Integer id, int delta) {
   A a = map.get(a);
   if(a == null) {
    a = new A(id);
    map.put(id,a);
   }
   a.setX(a.getX() + delta);
 }
}

TroveAPIもご覧ください。これは、パフォーマンスとプリミティブ変数を使用しているアカウンティングには優れていますが、この機能を非常にうまく公開しています(例:map.adjustOrPutValue(key、initialValue、deltaValue)。

0
Josh

少し範囲外ですが、hashCode()を再実装するのを忘れていました。 equalsをオーバーライドするときは、例であっても、hashCode()をオーバーライドしてください。

例えば; HashSetはObjectのhashCodeを使用してバケット(ビジネスロジックとは関係のない数値)を見つけ、その中の要素のみがequals()であるため、SetのHashSet実装がある場合、contains()はおそらくうまくいかないでしょう。バケツ。

public class A {
  public int id;
  public B b;
  public int hashCode() {return id;} // simple and efficient enough for small Sets 
  public boolean equals(Object another) { 
    if (object == null || ! (object instanceOf A) ) {
      return false;
    }
    return this.id == ((A)another).id; 
   }
}
public class Logic {
  /**
   * Replace the element in data with the same id as element, or add element
   * to data when the id of element is not yet used by any A in data. 
   */
  public void update(Set<A> data, A element) {
    data.remove(element); // Safe even if the element is not in the Set
    data.add(element); 
  }
}

[〜#〜] edit [〜#〜] Yuvalは、Set.addが既存の要素を上書きせず、要素がまだコレクションにない場合にのみ追加することを正しく示しています( "is"はによって実装されます)等しい)

0
extraneon