Set
が他の要素と等しい要素を取得するための操作を提供しないのはなぜですか?
Set<Foo> set = ...;
...
Foo foo = new Foo(1, 2, 3);
Foo bar = set.get(foo); // get the Foo element from the Set that equals foo
Set
にbar
と等しい要素が含まれているかどうかを尋ねることができますが、その要素を取得できないのはなぜですか? :(
明確にするために、equals
メソッドはオーバーライドされていますが、すべてではなくフィールドの1つだけをチェックします。したがって、等しいと見なされる2つのFoo
オブジェクトは実際には異なる値を持つことができます。そのため、foo
を使用することはできません。
等しい場合、要素を取得する意味はありません。 Map
は、このユースケースに適しています。
それでも要素を見つけたい場合は、反復子を使用する以外に選択肢はありません。
public static void main(String[] args) {
Set<Foo> set = new HashSet<Foo>();
set.add(new Foo("Hello"));
for (Iterator<Foo> it = set.iterator(); it.hasNext(); ) {
Foo f = it.next();
if (f.equals(new Foo("Hello")))
System.out.println("foo found");
}
}
static class Foo {
String string;
Foo(String string) {
this.string = string;
}
@Override
public int hashCode() {
return string.hashCode();
}
@Override
public boolean equals(Object obj) {
return string.equals(((Foo) obj).string);
}
}
「WhyがSet
が別の要素に等しい要素を取得する操作を提供しない」という正確な質問に答えるには、答えは次のとおりです。コレクションフレームワークの設計者は非常に楽しみです。彼らはあなたの非常に正当なユースケースを予期せず、単純に「javadocからの」数学セット抽象化のモデル化を試み、有用なget()
メソッドを追加するのを忘れました。
暗黙の質問「howでは要素を取得しますか?」:最善の解決策は、Map<E,E>
の代わりにSet<E>
を使用して要素をマッピングすることです自分自身。そのようにして、効率的に「set」から要素を取得できます。これは、Map
のget()メソッドが効率的なハッシュテーブルまたはツリーアルゴリズムを使用する要素。必要に応じて、Set
をカプセル化して、追加のget()
メソッドを提供するMap
の独自の実装を作成できます。
私の意見では、以下の答えは悪いか間違っています:
「すでに同じオブジェクトを持っているので、要素を取得する必要はありません」:質問ですでに示したように、アサーションは間違っています。等しい2つのオブジェクトは、オブジェクトの等価性に関係のない異なる状態を保持できます。目標は、「クエリ」として使用されるオブジェクトの状態ではなく、Set
に含まれる要素のこの状態にアクセスすることです。
「イテレータを使用する以外に選択肢はありません」:これは、大きなセットに対して完全に非効率的なコレクションに対する線形検索です(皮肉なことに、Set
は効率的に照会できるハッシュマップまたはツリーとして編成されます)。しないでください!このアプローチを使用することで、実際のシステムで深刻なパフォーマンスの問題を見てきました。私の意見では、欠落しているget()
メソッドについて恐ろしいことは、それを回避するのは少し面倒ではありませんが、ほとんどのプログラマーはその意味を考えずに線形検索アプローチを使用します。
集合をリストに変換してから、リストのget
メソッドを使用する
Set<Foo> set = ...;
List<Foo> list = new ArrayList<Foo>(set);
Foo obj = list.get(0);
あなたが同じ物を持っているなら、なぜあなたはセットからの物が必要なのですか?それがキーだけで「等しい」場合、Map
がより良い選択です。
とにかく、次のようにします。
Foo getEqual(Foo sample, Set<Foo> all) {
for (Foo one : all) {
if (one.equals(sample)) {
return one;
}
}
return null;
}
Java 8では、これはワンライナーになることができます。
return all.stream().filter(sample::equals).findAny().orElse(null);
Javaのデフォルト設定は、残念ながら、 jschreiner が正確に説明されているように、「get」操作を提供するようには設計されていません。
( dacwe によって推奨される)関心のある要素を見つけるために反復子を使用したり、( KyleM によって推奨される)要素を削除して再追加する解決策しかし、非常に非効率的になる可能性があります。
David Ogren で正しく記述されているように、等しくないオブジェクトが「等しい」ようにequalsの実装をオーバーライドすると、メンテナンスの問題が発生しやすくなります。
そして、(多くの人が示唆しているように)明示的な置き換えとしてMapを使用すると、コードが洗練されなくなります。
目的がセットに含まれている要素の元のインスタンスにアクセスすることである場合(私が正しくユースケースを理解していることを願ってください)、これは別の可能な解決策です。
Javaを使ってクライアントサーバー型のビデオゲームを開発している間も、私は個人的に同じニーズを持っていました。私の場合、各クライアントにはサーバーに格納されているコンポーネントのコピーがあり、問題はクライアントがサーバーのオブジェクトを変更する必要があるたびに発生していました。
インターネットを介してオブジェクトを渡すことは、クライアントがとにかくそのオブジェクトの異なるインスタンスを持っていたことを意味しました。この「コピーされた」インスタンスを元のインスタンスと一致させるために、Java UUIDを使用することにしました。
そこで私は抽象クラスUniqueItemを作成しました。これはそのサブクラスの各インスタンスに自動的にランダムな一意のIDを与えます。
このUUIDはクライアントとサーバーインスタンスの間で共有されているので、このように単純にMapを使用することでそれらを一致させることができます。
しかし、同様のユースケースでMapを直接使用するのは、まだ洗練されていません。誰かが、マップを使うことは保守と取り扱いがより複雑かもしれないと主張するかもしれません。
これらの理由で、私はMagicSetと呼ばれるライブラリを実装しました。これはMapの使用を開発者にとって「透明」にします。
https://github.com/ricpacca/magicset
元のJava HashSetと同様に、MagicHashSet(ライブラリで提供されているMagicSetの実装の1つ)は、バッキングHashMapを使用します。そして値としての要素それ自身。これは通常のHashSetと比較してメモリ使用量のオーバーヘッドを引き起こしません。
さらに、MagicSetはSetとまったく同じように使用できますが、getFromId()、popFromId()、removeFromId()など、追加の機能を提供するメソッドがいくつかあります。
これを使用する唯一の要件は、MagicSetに格納したい要素はすべて抽象クラスUniqueItemを拡張する必要があるということです。
これは、同じUUID(またはそのUUIDさえも)を持つその都市の別のインスタンスを指定して、MagicSetからその都市の元のインスタンスを取得することを想像したコード例です。
class City extends UniqueItem {
// Somewhere in this class
public void doSomething() {
// Whatever
}
}
public class GameMap {
private MagicSet<City> cities;
public GameMap(Collection<City> cities) {
cities = new MagicHashSet<>(cities);
}
/*
* cityId is the UUID of the city you want to retrieve.
* If you have a copied instance of that city, you can simply
* call copiedCity.getId() and pass the return value to this method.
*/
public void doSomethingInCity(UUID cityId) {
City city = cities.getFromId(cityId);
city.doSomething();
}
// Other methods can be called on a MagicSet too
}
あなたのセットが実際にNavigableSet<Foo>
(TreeSet
など)、そしてFoo implements Comparable<Foo>
であるなら、あなたは使うことができます。
Foo bar = set.floor(foo); // or .ceiling
if (foo.equals(bar)) {
// use bar…
}
(ヒントについての@ eliran-malkaのコメントに感謝します。)
Java 8では、次のことが可能です。
Foo foo = set.stream().filter(item->item.equals(theItemYouAreLookingFor)).findFirst().get();
しかし、注意してください、.get()はNoSuchElementExceptionを投げます、あるいはOptional項目を操作することができます。
Object objectToGet = ...
Map<Object, Object> map = new HashMap<Object, Object>(set.size());
for (Object o : set) {
map.put(o, o);
}
Object objectFromSet = map.get(objectToGet);
あなたが1つだけ取得するならば、あなたはすべてのあなたの要素をループするのであなたは違いを気づくでしょうがあなたがすべてのあなたの要素をループするのでそれほどパフォーマンスが良くないでしょう。
なぜ:
Setは比較の手段を提供するのに役立つ役割を果たしているようです。重複要素を格納しないように設計されています。
この意図/設計のために、格納されたオブジェクトへの参照をget()してから変更すると、Setの設計意図が妨げられ、予期しない動作が発生する可能性があります。
可変オブジェクトを集合要素として使用する場合は、細心の注意を払う必要があります。オブジェクトがセット内の要素である間に、オブジェクトの値が等号比較に影響を与えるような方法で変更された場合、セットの動作は指定されません。
どうやって:
Streamsが導入されたので、次のことができます。
mySet.stream()
.filter(object -> object.property.equals(myProperty))
.findFirst().get();
そのためにはJava HashMapオブジェクトを使用することをお勧めします http://download.Oracle.com/javase/1,5.0/docs/api/Java/util/HashMap.html
Setのどんな特定の実装もそうではないかもしれないので ランダムアクセス 。
イテレータのnext()
メソッドを使って、等しい要素が見つかったら、いつでも イテレータ を取得してSetをステップスルーすることができます。これは実装に関係なく機能します。実装がランダムアクセスではない場合(リンクリスト付きのSetセットの画像)、返す要素を見つけるためにコレクションを繰り返す必要があり、get(E element)
はこれを暗示しているように思われるので、インターフェースのget(E element)
メソッドは欺くでしょうSetが取得する要素に直接ジャンプできることが必要です。
contains()
は、もちろん実装によっては同じことをする必要があるかもしれませんし、そうでないかもしれませんが、その名前が同じ種類の誤解に役立つとは思えません。
あなたがHashSetからn番目の要素が欲しいなら、あなたは以下の解決策で進むことができます、ここで私はHashSetでModelClassのオブジェクトを加えました。
ModelClass m1 = null;
int nth=scanner.nextInt();
for(int index=0;index<hashset1.size();index++){
m1 = (ModelClass) itr.next();
if(nth == index) {
System.out.println(m1);
break;
}
}
iteratorクラスを使用できます
import Java.util.Iterator;
import Java.util.HashSet;
public class MyClass {
public static void main(String[ ] args) {
HashSet<String> animals = new HashSet<String>();
animals.add("fox");
animals.add("cat");
animals.add("dog");
animals.add("rabbit");
Iterator<String> it = animals.iterator();
while(it.hasNext()) {
String value = it.next();
System.out.println(value);
}
}
}
Java.util.HashSet
の実装の最初の数行を見ると、次のようになります。
public class HashSet<E>
....
private transient HashMap<E,Object> map;
そのため、HashSet
はHashMap
を内部的に使用します。つまり、HashMap
を直接使用し、キーと値として同じ値を使用すると、必要な効果が得られ、メモリを節約できます。
私は知っている、これはずっと前に質問されて答えられた、しかし誰かが興味を持っているならば、これが私の解決策である - HashMapによって支持されるカスタムセットクラス
他のすべてのSetメソッドを簡単に実装できます。
はい、HashMap
を使用してください。ただし、特殊な方法で使用します。HashMap
を疑似-Set
として使用しようとしたときに予想されるトラップは、Map/Set
の「実際の」要素と「候補」要素の間の混乱です。 equal
要素が既に存在するかどうかをテストします。これは絶対確実というわけではありませんが、罠からあなたを遠ざけます。
class SelfMappingHashMap<V> extends HashMap<V, V>{
@Override
public String toString(){
// otherwise you get lots of "... object1=object1, object2=object2..." stuff
return keySet().toString();
}
@Override
public V get( Object key ){
throw new UnsupportedOperationException( "use tryToGetRealFromCandidate()");
}
@Override
public V put( V key, V value ){
// thorny issue here: if you were indavertently to `put`
// a "candidate instance" with the element already in the `Map/Set`:
// these will obviously be considered equivalent
assert key.equals( value );
return super.put( key, value );
}
public V tryToGetRealFromCandidate( V key ){
return super.get(key);
}
}
それからこれをしなさい:
SelfMappingHashMap<SomeClass> selfMap = new SelfMappingHashMap<SomeClass>();
...
SomeClass candidate = new SomeClass();
if( selfMap.contains( candidate ) ){
SomeClass realThing = selfMap.tryToGetRealFromCandidate( candidate );
...
realThing.useInSomeWay()...
}
しかし、プログラマが実際にすぐにMap/Set
..に入れない限り、candidate
を何らかの形で自己消滅させる必要があります。contains
を結合しない限り、candidate
を「汚染」させたいのです。 Map
はそれを "アナテマ"にします。おそらくSomeClass
に新しいTaintable
インターフェースを実装させることができます。
以下のように、より満足のいく解決策はGettableSetです。しかし、これをうまく機能させるには、すべてのコンストラクタを見えないようにするためにSomeClass
の設計を担当する必要があります(または...可能であり、ラッパークラスを設計して使用する意欲があります)。
public interface NoVisibleConstructor {
// again, this is a "Nudge" technique, in the sense that there is no known method of
// making an interface enforce "no visible constructor" in its implementing classes
// - of course when Java finally implements full multiple inheritance some reflection
// technique might be used...
NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet );
};
public interface GettableSet<V extends NoVisibleConstructor> extends Set<V> {
V getGenuineFromImpostor( V impostor ); // see below for naming
}
実装:
public class GettableHashSet<V extends NoVisibleConstructor> implements GettableSet<V> {
private Map<V, V> map = new HashMap<V, V>();
@Override
public V getGenuineFromImpostor(V impostor ) {
return map.get( impostor );
}
@Override
public int size() {
return map.size();
}
@Override
public boolean contains(Object o) {
return map.containsKey( o );
}
@Override
public boolean add(V e) {
assert e != null;
V result = map.put( e, e );
return result != null;
}
@Override
public boolean remove(Object o) {
V result = map.remove( o );
return result != null;
}
@Override
public boolean addAll(Collection<? extends V> c) {
// for example:
throw new UnsupportedOperationException();
}
@Override
public void clear() {
map.clear();
}
// implement the other methods from Set ...
}
あなたのNoVisibleConstructor
クラスはこのようになります:
class SomeClass implements NoVisibleConstructor {
private SomeClass( Object param1, Object param2 ){
// ...
}
static SomeClass getOrCreate( GettableSet<SomeClass> gettableSet, Object param1, Object param2 ) {
SomeClass candidate = new SomeClass( param1, param2 );
if (gettableSet.contains(candidate)) {
// obviously this then means that the candidate "fails" (or is revealed
// to be an "impostor" if you will). Return the existing element:
return gettableSet.getGenuineFromImpostor(candidate);
}
gettableSet.add( candidate );
return candidate;
}
@Override
public NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet ){
// more elegant implementation-hiding: see below
}
}
PSそのようなNoVisibleConstructor
クラスに関する1つの技術的な問題:そのようなクラスが本質的にfinal
であることは反対されるかもしれませんが、それは望ましくないかもしれません。実際には、いつでもダミーのパラメータなしのprotected
コンストラクタを追加できます。
protected SomeClass(){
throw new UnsupportedOperationException();
}
...これは少なくともサブクラスをコンパイルさせるでしょう。その後、サブクラスに別のgetOrCreate()
ファクトリメソッドを含める必要があるかどうかを検討する必要があります。
最後のステップ はあなたのセットメンバーのためのこのような抽象基底クラス(リストの場合は "element"、セットの場合は "member")です(可能であれば、wrapper classの使用範囲最大限の実装を隠蔽するための、クラスがあなたの管理下にない、または既に基本クラスを持っているなどの場合。
public abstract class AbstractSetMember implements NoVisibleConstructor {
@Override
public NoVisibleConstructor
addOrGetExisting(GettableSet<? extends NoVisibleConstructor> gettableSet) {
AbstractSetMember member = this;
@SuppressWarnings("unchecked") // unavoidable!
GettableSet<AbstractSetMembers> set = (GettableSet<AbstractSetMember>) gettableSet;
if (gettableSet.contains( member )) {
member = set.getGenuineFromImpostor( member );
cleanUpAfterFindingGenuine( set );
} else {
addNewToSet( set );
}
return member;
}
abstract public void addNewToSet(GettableSet<? extends AbstractSetMember> gettableSet );
abstract public void cleanUpAfterFindingGenuine(GettableSet<? extends AbstractSetMember> gettableSet );
}
...使い方はかなり明白です(あなたのSomeClass
のstatic
ファクトリメソッドの内側):
SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );
やったことある!あなたがGuavaを使っているならば、それを地図に変換する簡単な方法は:
Map<Integer,Foo> map = Maps.uniqueIndex(fooSet, Foo::getKey);