为什么 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
方法被覆盖,但它只检查其中一个字段,而不是所有字段 . 因此,被认为相等的两个 Foo
对象实际上可以具有不同的值,'s why I can' t只使用 foo
.
20 回答
您最好为此目的使用Java HashMap对象http://download.oracle.com/javase/1,5.0/docs/api/java/util/HashMap.html
如果元素相等,就没有必要获得元素 .
Map
更适合此用例 .如果您仍想查找元素,则除了使用迭代器之外没有其他选择:
要回答准确的问题“ Why 不提供一个操作来获得一个等于另一个元素的元素?", the answer would be: because the designers of the collection framework were not very forward looking. They didn't anticipate your very legitimate use case, naively tried to "模型数学集抽象”(来自javadoc)并且忘记添加有用的
get()
方法 .现在来到隐含的问题“ how 你得到的元素然后”:我认为最好的解决方案是使用
Map<E,E>
而不是Set<E>
,将元素映射到自己 . 这样,您就可以有效地从"set"中检索元素,因为Map
的get()方法将使用有效的哈希表或树算法找到该元素 . 如果你愿意,你可以编写自己的Set
实现,提供额外的get()
方法,封装Map
.以下答案在我看来是坏的还是错的:
"You don't need to get the element, because you already have an equal object":断言是错误的,正如您在问题中已经表明的那样 . 两个相等的对象仍然可以具有与对象相等无关的不同状态 . 目标是访问
Set
中包含的元素的这种状态,而不是用作"query"的对象的状态 ."You have no other option but to use the iterator":这是对集合的线性搜索,对于大集合来说效率非常低(具有讽刺意味的是,
Set
内部被组织为可以有效查询的哈希映射或树) . 不要这样做!通过使用该方法,我已经在现实系统中看到了严重的性能问题 . 在我看来,缺少get()
方法的可怕之处并不在于解决它有点麻烦,但是大多数程序员将使用线性搜索方法而不考虑其含义 .将set转换为list,然后使用
get
list方法如果你有一个相同的对象,为什么你需要一个对象?如果它只是一个键"equal",那么
Map
将是更好的选择 .无论如何,以下将做到:
使用Java 8,这可以成为一个单行:
遗憾的是,Java中的默认设置并非旨在提供"get"操作,正如jschreiner准确解释的那样 .
使用迭代器来查找感兴趣的元素(由dacwe建议)或删除元素并重新添加其更新值(由KyleM建议)的解决方案可能有效,但效率可能非常低 .
重写equals的实现,以便"equal"正确地说明不相等的对象"equal",很容易导致维护问题 .
使用Map作为显式替换(如许多人的建议),imho使代码不那么优雅 .
如果目标是访问集合中包含的元素的原始实例(希望我能正确理解您的用例),这是另一种可能的解决方案 .
在使用Java开发客户端 - 服务器视频游戏时,我个人也有同样的需求 . 就我而言,每个客户端都有存储在服务器中的组件的副本,问题是客户端需要修改服务器的对象 .
通过互联网传递对象意味着客户端无论如何都有该对象的不同实例 . 为了将这个“复制”的实例与原始实例相匹配,我决定使用Java UUID .
所以我创建了一个抽象类UniqueItem,它自动为其子类的每个实例提供一个随机唯一id .
此UUID在客户端和服务器实例之间共享,因此通过简单地使用Map可以很容易地匹配它们 .
然而,在类似的用例中直接使用Map仍然不够优雅 . 有人可能会争辩说,使用Map可能会更难以保持和处理 .
出于这些原因,我实现了一个名为MagicSet的库,它使用了一个将“透明”映射到开发人员 .
https://github.com/ricpacca/magicset
与原始Java HashSet一样,MagicHashSet(它是库中提供的MagicSet的一个实现)使用支持HashMap,但它不使用元素作为键和虚拟值作为值,而是使用元素的UUID作为键和元素本身作为 Value . 与普通的HashSet相比,这不会导致内存使用的开销 .
此外,MagicSet可以完全用作Set,但有一些更多的方法可以提供额外的功能,如getFromId(),popFromId(),removeFromId()等 .
使用它的唯一要求是您要存储在MagicSet中的任何元素都需要扩展抽象类UniqueItem .
下面是一个代码示例,想象从MagicSet中检索城市的原始实例,给定该城市的另一个实例具有相同的UUID(甚至只是其UUID) .
如果您的集合实际上是
NavigableSet<Foo>
(例如TreeSet
)和Foo implements Comparable<Foo>
,则可以使用(感谢@ eliran-malka对提示的评论 . )
使用Java 8,您可以:
但要小心,.get()会抛出NoSuchElementException,或者您可以操作Optional项 .
如果你只做一个得到这将不会很好,因为你将遍历所有元素,但在大集上执行多次检索时你会发现差异 .
为什么:
似乎Set在提供比较手段方面发挥了有用的作用 . 它旨在不存储重复元素 .
由于这种意图/设计,如果要获得()对存储对象的引用,然后对其进行修改,则可能会阻止Set的设计意图并可能导致意外行为 .
来自JavaDocs
怎么样:
现在已经介绍了Streams,可以执行以下操作
因为Set的任何特定实现可能是也可能不是random access .
你总是得到一个iterator并逐步执行Set,使用迭代器的
next()
方法在找到相等的元素后返回你想要的结果 . 无论实现如何,这都有效 . 如果实现不是随机访问(图片是链接列表支持的Set),则接口中的get(E element)
方法将具有欺骗性,因为它必须迭代集合以找到要返回的元素,并且get(E element)
似乎暗示了这一点有必要,Set可以直接跳转到要获取的元素 .当然,取决于实施情况,
contains()
可能会也可能不会做同样的事情,但这个名称似乎不会导致同样的误解 .我知道,很久以前就已经提出并回答了这个问题,但是如果有人有兴趣,这是我的解决方案 - 由HashMap支持的自定义集合类:
http://pastebin.com/Qv6S91n9
您可以轻松实现所有其他Set方法 .
去过也做过!!如果您正在使用Guava,可以快速将其转换为 Map :
是的,使用
HashMap
...但是以一种特殊的方式:我试图使用HashMap
作为伪Set
的陷阱是Map/Set
和"candidate"元素的"actual"元素之间可能存在的混淆,即用于测试是否为equal
元素已存在 . 这远非万无一失,但是让你远离陷阱:然后这样做:
但是......你现在希望
candidate
以某种方式自毁,除非程序员实际上立即将它放入Map/Set
...你想要contains
"taint"candidate
以便任何使用它,除非它加入Map
使它"anathema" . 也许你可以让SomeClass
实现一个新的Taintable
接口 .更令人满意的解决方案是 GettableSet ,如下所示 . 但是,要实现这一点,您要么负责
SomeClass
的设计,以使所有构造函数不可见(或者......能够并且愿意为它设计和使用包装类):执行:
你的
NoVisibleConstructor
类看起来像这样:PS这样一个技术问题
NoVisibleConstructor
class:可能有人反对说这样的类本质上是final
,这可能是不合需要的 . 实际上你总是可以添加一个虚拟的无参数protected
构造函数:...至少会让子类编译 . 然后,您必须考虑是否需要在子类中包含另一个
getOrCreate()
工厂方法 .Final step 是一个抽象基类(对于列表为NB "element",对于一个集合为"member"),对于您的集合成员是这样的(如果可能的话 - 再次,使用包装类的范围,其中类不在您的控制之下,或者已经有一个基础类等),用于最大限度地实现隐藏:
...用法相当明显(在
SomeClass
的static
工厂方法中):如果你想要来自HashSet的第n个元素,你可以使用下面的解决方案,这里我在HashSet中添加了ModelClass的对象 .
如果你看一下
java.util.HashSet
实现的前几行,你会看到:所以
HashSet
无论如何都使用HashMap
,这意味着如果你直接使用HashMap
并使用与键和值相同的值,你将得到你想要的效果并节省一些内存 .可以解决这种情况的快速帮助方法:
你可以使用Iterator类
以下可以是一种方法
尝试使用数组: