从集合中获取元素

为什么不提供一个操作来获取一个元素,等于另一个元素?

 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方法被覆盖,但它只检查其中一个字段,而不是全部。 所以两个被认为相等的Foo对象实际上可以有不同的值,这就是为什么我不能使用foo

如果平等的话,就没有必要得到这个元素。 一个Map更适合这个用例。


如果你仍然想find这个元素,你只能使用迭代器:

 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); } } 

为了回答“ 为什么不提供一个操作来获取等于另一个元素的元素?”这个确切的问题,答案将是:因为收集框架的devise者不是很有前瞻性。 他们没有预料到你的合法用例,他们天真地试图“模拟math集抽象”(从javadoc中),只是忘了添加有用的get()方法。

现在隐含的问题是“ 如何获取元素”:我认为最好的解决scheme是使用Map<E,E>而不是Set<E>来将元素映射到自己。 通过这种方式,您可以高效地从“集合”中检索元素,因为Map的get()方法将使用高效的散列表或树algorithm来查找元素。 如果你想,你可以编写自己的Set实现,提供额外的get()方法,封装Map

以下答案在我看来是不好的或错误的:

“你不需要得到元素,因为你已经有了一个平等的对象”:这个断言是错误的,正如你已经在问题中显示的那样。 两个平等的对象仍然可以有不同的状态,与对象的平等无关。 目标是访问Set包含的元素的状态,而不是用作“查询”的对象的状态。

“你没有其他select,只能使用迭代器”:这是对集合的线性search,对于大集合来说是完全没有效率的(具有讽刺意味的是, Set在内部被组织为可以被有效查询的哈希映射或树)。 不要这样做! 通过使用这种方法,我在实际系统中看到了严重的性能问题。 在我看来, get()方法的糟糕之处并不在于如何解决这个问题,而是大多数程序员都会使用线性search方法而不考虑其含义。

将设置转换为列表,然后使用列表的get方法

 Set<Foo> set = ...; List<Foo> list = new ArrayList<Foo>(set); Foo obj = list.get(0); 

如果你的集合实际上是一个NavigableSet<Foo> (比如一个TreeSet ),并且Foo implements Comparable<Foo> ,那么你可以使用

 Foo bar = set.floor(foo); // or .ceiling if (foo.equals(bar)) { // use bar… } 

(感谢@ eliran-malka对提示的评论。)

如果你有一个平等的对象,为什么你需要从集合中的那个? 如果只用一把钥匙“相等”,一张Map将是一个更好的select。

无论如何,以下将做到这一点:

 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); 
 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); 

如果你只做一个得到这个不会很好的performance,因为你会循环所有的元素,但是当一个大集合执行多个检索时,你会注意到不同之处。

不幸的是,Java中的默认设置并不是为了提供一个“get”操作,正如jschreiner准确地解释的那样。

使用迭代器来查找感兴趣的元素(由dacwebuild议)或删除元素并重新添加其值(由KyleMbuild议)的解决scheme可以工作,但可能是非常低效的。

正如戴维·奥格伦 ( David Ogren)所正确指出的,重写等式的实现以便不等于“相等”的对象很容易引起维护问题。

使用Map作为显式replace(如许多人所build议的),使得代码不那么优雅。

如果目标是访问包含在集合中的元素的原始实例(希望我正确理解你的用例),这里是另一个可能的解决scheme。


在使用Java开发客户端服务器video游戏时,我个人也有同样的需求。 在我的情况下,每个客户端都有存储在服务器中的组件的副本,问题是客户端需要修改服务器的对象。

通过互联网传递对象意味着客户无论如何都有不同的对象实例。 为了将这个“复制的”实例与原来的实例相匹配,我决定使用Java UUID。

所以我创build了一个抽象类UniqueItem,它自动给它的子类的每个实例赋予一个随机唯一的ID。

这个UUID是在客户端和服务器实例之间共享的,所以通过简单地使用Map就可以很容易地匹配它们。

然而,直接在类似的用例中使用Map还是不够的。 有人可能会争辩说,使用Map可能会更复杂,以保持和处理。

由于这些原因,我实现了一个名为MagicSet的库,使开发人员可以使用Map“透明”。

https://github.com/ricpacca/magicset


像原始的Java HashSet一样,MagicHashSet(它是库中提供的MagicSet的一个实现)使用了一个支持HashMap,但是不是将元素作为键,而是将一个虚拟值作为值,它使用元素的UUID作为键和元素本身作为价值。 与正常的HashSet相比,这不会导致内存使用的开销。

而且,MagicSet可以像Set一样使用,但还有一些方法可以提供额外的function,比如getFromId(),popFromId(),removeFromId()等等。

使用它的唯一要求是你想存储在MagicSet中的任何元素都需要扩展抽象类UniqueItem。


这里是一个代码示例,想象从MagicSet中检索城市的原始实例,给定具有相同UUID(甚至仅仅是其UUID)的该城市的另一个实例。

 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 } 

我知道,这已被问及很久以前回答,但是如果有人感兴趣,这里是我的解决scheme – 自定义设置类由HashMap支持:

http://pastebin.com/Qv6S91n9

您可以轻松实现所有其他Set方法。

快速辅助方法,可能会解决这种情况:

 <T> T onlyItem(Collection<T> items) { if (items.size() != 1) throw new IllegalArgumentException("Collection must have single item; instead it has " + items.size()); return items.iterator().next(); } 

有了Java 8,你可以这样做:

 Foo foo = set.stream().filter(item->item.equals(theItemYouAreLookingFor)).findFirst().get(); 

但要小心,.get()会抛出一个NoSuchElementException,或者您可以操作一个可选项目。

如果你想从HashSet的第n个元素,你可以去下面的解决scheme,在这里我已经在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; } } 

因为Set的任何特定的实现可能是也可能不是随机访问 。

你总是可以得到一个迭代器,并遍历Set,使用迭代器的next()方法返回你想要的结果,一旦你find了相等的元素。 这个工作不pipe实现。 如果实现不是随机访问(图片链接列表支持Set),那么接口中的get(E element)方法将是欺骗性的,因为它将不得不迭代集合以find要返回的元素,并且get(E element)似乎意味着这将是必要的,该集可以直接跳到元素获取。

contains()可能也可能不需要做同样的事情,当然,这取决于实现,但这个名字似乎并不适用于同样的误解。

也许使用一个数组:

ObjectClass [] arrayName = SetOfObjects.toArray(new ObjectClass [setOfObjects.size()]);

去过也做过!! 如果您正在使用番石榴一个快速的方式将其转换为地图是:

 Map<Integer,Foo> map = Maps.uniqueIndex(fooSet, Foo::getKey); 

是的,使用HashMap …但以一种专门的方式:试图将HashMap用作伪SetHashMap陷阱是Map/Set “实际”元素和“候选”元素之间可能的混淆,即用于testing是否存在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()... } 

但是……现在你想让candidate以某种方式自毁,除非程序员实际上立即把它放在Map/Set ……你想要contains “污染” candidate ,除非它使用它joinMap使其成为“诅咒”。 也许你可以让SomeClass实现一个新的Taintable接口。

更令人满意的解决scheme是GettableSet ,如下所示。 然而,为了这个工作,你要么负责SomeClass的devise,以使所有构造函数不可见(或者…能够并且愿意为它devise和使用包装类):

 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类的技术问题:可能会反对这样一个类本质上是final ,这可能是不可取的。 其实你总是可以添加一个虚拟无参数protected构造函数:

 protected SomeClass(){ throw new UnsupportedOperationException(); } 

…至less会让一个子类编译。 那么你必须考虑是否需要在子类中包含另一个getOrCreate()工厂方法。

最后一步是一个抽象基类(列表中的“NB元素”,列表中的“元素”),像这样的集合成员(如果可能的话 – 也是使用包含类的范围,或已经有一个基类,等等),为最大的实现隐藏:

 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 ); } 

…用法相当明显(在SomeClassstatic工厂方法中):

 SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set ); 

为什么:

Set似乎在提供比较手段方面起到了有益的作用。 它被devise成不存储重复的元素。

由于这个意图/devise,如果有人要()引用存储的对象,然后进行变异,Set的devise意图就有可能被挫败并可能导致意想不到的行为。

从JavaDocs

如果将可变对象用作设置元素,必须非常小心。 如果对象的值以影响等于比较的方式进行更改,而对象是集合中的元素,则不会指定集合的​​行为。

怎么样:

现在Streams已经被引入,可以做到以下几点

 mySet.stream() .filter(object -> object.property.equals(myProperty)) .findFirst().get(); 

你可以使用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); } } } 

以下可以是一种方法

  SharedPreferences se_get = getSharedPreferences("points",MODE_PRIVATE); Set<String> main = se_get.getStringSet("mydata",null); for(int jk = 0 ; jk < main.size();jk++) { Log.i("data",String.valueOf(main.toArray()[jk])); }