从集合中获取元素
为什么不提供一个操作来获取一个元素,等于另一个元素?
 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 } 
为此,最好使用Java HashMap对象http://download.oracle.com/javase/1,5.0/docs/api/java/util/HashMap.html
我知道,这已被问及很久以前回答,但是如果有人感兴趣,这里是我的解决scheme – 自定义设置类由HashMap支持:
您可以轻松实现所有其他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用作伪Set的HashMap陷阱是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 ); } 
  …用法相当明显(在SomeClass的static工厂方法中): 
 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])); }