为什么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对象可以有不同的值,这就是为什么我不能只用Foo。


当前回答

哈希码的契约清楚地表明:

如果根据Object方法,两个对象是相等的,那么在这两个对象上调用hashCode方法必须产生相同的整数结果。

所以你的假设是

为了澄清,equals方法被重写,但它只检查其中之一 田地,不是全部。所以两个相等的Foo对象可以 有不同的值,这就是为什么我不能只使用foo。”

是错误的,你违反了合同。如果我们看Set接口的"contains"方法,我们有:

boolean contains(Object o); 如果此集合包含指定的元素,则返回true。更多的 形式上,当且仅当此集合包含元素时返回true "e"使得o==null ?E ==null: o. = (E)

为了实现您想要的效果,您可以使用Map,在其中定义键并使用定义对象如何彼此不同或相等的键存储元素。

其他回答

如果你的set实际上是一个NavigableSet<Foo>(例如TreeSet),并且Foo实现Comparable<Foo>,你可以使用

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

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

尝试使用数组:

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

要准确回答“为什么Set不提供获取等于另一个元素的元素的操作?”这个问题,答案可能是:因为集合框架的设计者不是很有前瞻性。他们没有预料到您非常合理的用例,天真地试图“建模数学集合抽象”(从javadoc),只是忘记添加有用的get()方法。

现在回到隐含的问题“如何获得元素”:我认为最好的解决方案是使用Map<E,E>而不是Set<E>来将元素映射到它们自己。通过这种方式,您可以有效地从“set”中检索元素,因为Map的get()方法将使用有效的哈希表或树算法查找元素。如果愿意,可以编写自己的Set实现,提供额外的get()方法,封装Map。

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

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

"You have no other option but to use the iterator": that is a linear search over a collection which is totally inefficient for large sets (ironically, internally the Set is organized as hash map or tree that could be queried efficiently). Don't do it! I have seen severe performance problems in real-life systems by using that approach. In my opinion what is terrible about the missing get() method is not so much that it is a bit cumbersome to work around it, but that most programmers will use the linear search approach without thinking of the implications.

Why:

Set似乎在提供比较手段方面发挥了有用的作用。它被设计为不存储重复的元素。

由于这种意图/设计,如果要获得()对存储对象的引用,然后更改它,则Set的设计意图可能会受到阻碍,并可能导致意想不到的行为。

来自JavaDocs

如果使用可变对象作为set元素,必须非常小心。当对象是集合中的元素时,如果对象的值以影响相等比较的方式更改,则不指定集合的行为。

How:

现在已经引入了流,我们可以做以下事情

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

我在那里做过!!如果你正在使用番石榴,一个快速的方法将它转换为地图是:

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