foo*_*bar 297 java collections equals set
为什么不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
Run Code Online (Sandbox Code Playgroud)
我可以问一下是否Set
包含一个等于的元素bar
,为什么我不能得到那个元素?:(
为了澄清,该equals
方法被覆盖,但它只检查其中一个字段,而不是所有字段.因此,两个Foo
被认为相等的对象实际上可以具有不同的值,这就是我不能使用的原因foo
.
jsc*_*ner 353
为了回答准确的问题" 为什么不Set
提供一个操作来获得与另一个元素相等的元素?",答案是:因为集合框架的设计者不是非常具有前瞻性.他们没有预料到你的合法用例,天真地试图"模拟数学集抽象"(来自javadoc)并且忘记添加有用的get()
方法.
现在来到隐含的问题" 你如何获得元素":我认为最好的解决方案是使用a Map<E,E>
而不是a Set<E>
来将元素映射到自己.这样,您就可以从"set"中有效地检索元素,因为get的()方法Map
将使用有效的哈希表或树算法来查找元素.如果你愿意,你可以编写自己的实现Set
,提供额外的get()
方法,封装Map
.
以下答案在我看来是坏的还是错的:
"你不需要获取元素,因为你已经有了一个相同的对象":断言是错误的,正如你已经在问题中所表明的那样.两个相等的对象仍然可以具有与对象相等无关的不同状态.目标是访问包含在该元素中的元素Set
的状态,而不是用作"查询"的对象的状态.
"你没有别的选择,只能使用迭代器":这是对集合的线性搜索,对大型集合来说效率非常低(具有讽刺意味的是,内部Set
组织为可以有效查询的哈希映射或树).不要这样做!通过使用该方法,我已经在现实系统中看到了严重的性能问题.在我看来,丢失get()
方法的可怕之处并不在于解决它有点麻烦,但是大多数程序员将使用线性搜索方法而不考虑其含义.
dac*_*cwe 105
如果元素相等,就没有必要获得元素.A 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);
}
}
Run Code Online (Sandbox Code Playgroud)
To *_*Kra 21
将set转换为list,然后使用get
list的方法
Set<Foo> set = ...;
List<Foo> list = new ArrayList<Foo>(set);
Foo obj = list.get(0);
Run Code Online (Sandbox Code Playgroud)
Arn*_*ter 17
如果你有一个相同的对象,为什么你需要一个对象?如果它只是一个键"相等",那Map
将是一个更好的选择.
无论如何,以下将做到:
Foo getEqual(Foo sample, Set<Foo> all) {
for (Foo one : all) {
if (one.equals(sample)) {
return one;
}
}
return null;
}
Run Code Online (Sandbox Code Playgroud)
使用Java 8,这可以成为一个单行:
return all.stream().filter(sample::equals).findAny().orElse(null);
Run Code Online (Sandbox Code Playgroud)
ric*_*cca 14
遗憾的是,Java中的默认设置并非旨在提供"获取"操作,正如jschreiner准确解释的那样.
使用迭代器来查找感兴趣的元素(由dacwe建议)或删除元素并重新添加其更新值(由KyleM建议)的解决方案可能有效,但效率可能非常低.
正如David Ogren所说的那样,重写equals的实现以使不相等的对象"相等" 很容易导致维护问题.
使用Map作为显式替换(如许多人的建议),imho使代码不那么优雅.
如果目标是访问集合中包含的元素的原始实例(希望我能正确理解您的用例),这是另一种可能的解决方案.
在使用Java开发客户端 - 服务器视频游戏时,我个人也有同样的需求.就我而言,每个客户端都有存储在服务器中的组件的副本,问题是客户端需要修改服务器的对象.
通过互联网传递对象意味着客户端无论如何都有该对象的不同实例.为了将这个"复制"的实例与原始实例相匹配,我决定使用Java UUID.
所以我创建了一个抽象类UniqueItem,它自动为其子类的每个实例提供一个随机唯一id.
此UUID在客户端和服务器实例之间共享,因此通过简单地使用Map可以很容易地匹配它们.
然而,在类似的用例中直接使用Map仍然不够优雅.有人可能会争辩说,使用Map可能会更难以保持和处理.
出于这些原因,我实现了一个名为MagicSet的库,它使Map对开发人员的使用"透明".
https://github.com/ricpacca/magicset
与原始Java HashSet一样,MagicHashSet(它是库中提供的MagicSet的一个实现)使用支持HashMap,但它不使用元素作为键和虚拟值作为值,而是使用元素的UUID作为键和元素本身作为价值.与普通的HashSet相比,这不会导致内存使用的开销.
此外,MagicSet可以完全用作Set,但有一些更多的方法可以提供额外的功能,如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
}
Run Code Online (Sandbox Code Playgroud)
Jes*_*ick 11
如果您的集合实际上是NavigableSet<Foo>
(例如a TreeSet
)Foo implements Comparable<Foo>
,那么您可以使用
Foo bar = set.floor(foo); // or .ceiling
if (foo.equals(bar)) {
// use bar…
}
Run Code Online (Sandbox Code Playgroud)
(感谢@ eliran-malka对提示的评论.)
clo*_*her 10
使用Java 8,您可以:
Foo foo = set.stream().filter(item->item.equals(theItemYouAreLookingFor)).findFirst().get();
Run Code Online (Sandbox Code Playgroud)
但要小心,.get()会抛出NoSuchElementException,或者您可以操作Optional项.
小智 7
为什么:
似乎 Set 在提供比较方法方面发挥了有用的作用。它旨在不存储重复元素。
由于这种意图/设计,如果要 get() 对存储对象的引用,然后对其进行变异,则 Set 的设计意图可能会受阻并可能导致意外行为。
来自JavaDocs
如果将可变对象用作集合元素,则必须非常小心。如果对象的值以影响等于比较的方式更改,而对象是集合中的元素,则不会指定集合的行为。
如何:
现在已经引入了 Streams,可以执行以下操作
mySet.stream()
.filter(object -> object.property.equals(myProperty))
.findFirst().get();
Run Code Online (Sandbox Code Playgroud)
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);
Run Code Online (Sandbox Code Playgroud)
如果只执行一次操作,这将不会很好地执行,因为您将循环遍历所有元素,但是当对一个大集合执行多次检索时,您会注意到其中的区别。