我一直在向C ++移植一大堆Java代码,并且不得不实现像LinkedHashSet这样的东西。我使用Boost的多索引容器对LinkedHashSet / Map进行了合理的传真。
当我移植代码时,我正在使用multi_index运行一些有趣的东西,因为包含的对象不可变(除非你将类的特定字段标记为可变)。但是,如果密钥是从包含类的一些可变成员计算的,那么事情就会变得有趣。
为了澄清一些事情,我认为我会在Java中编写一个简单的例子来检查他们的LinkedHashSet的行为。结果对我来说有点意外;看起来它们的行为类似于Boost的多索引容器,因为当修改包含的对象时,索引不会被重新生成(正如您所期望的那样);然而,编译器并没有以任何方式抱怨 - 看起来很容易在脚下射击(我正在移植的代码似乎犯了上述罪,谁知道它是如何工作的。)
这只是Java中缺少const_iterators的限制,还是我设法做了一些特别愚蠢或棘手的事情?
这是一个简单的例子:
class StringContainer
{
public String s;
public StringContainer(String s)
{
this.s = s;
}
public boolean equals(Object t1)
{
StringContainer other = (StringContainer) t1;
return this.s == other.s;
}
public int hashCode()
{
int val = 8;
for (int i = 0; i < s.length(); i++)
val += s.charAt(i);
return val;
}
public String toString()
{
return s;
}
}
class test
{
public static void main(String[] args)
{
Set<StringContainer> set = new LinkedHashSet();
set.add(new StringContainer("Foo"));
set.add(new StringContainer("Bar"));
set.add(new StringContainer("Baz"));
set.add(new StringContainer("Qux"));
Iterator<StringContainer> it = set.iterator();
while (it.hasNext())
{
StringContainer s = it.next();
if (s.s == "Baz")
s.s = "Baz2";
System.out.println(s);
}
System.out.println("\nRe-iterate:\n");
it = set.iterator();
while (it.hasNext())
{
StringContainer s = it.next();
System.out.println(s);
}
System.out.println();
if (set.contains(new StringContainer("Foo")))
System.out.println("Contains Foo");
if (set.contains(new StringContainer("Baz")))
System.out.println("Contains Baz");
else
System.out.println("Does not contain Baz");
if (set.contains(new StringContainer("Baz2")))
System.out.println("Contains Baz2");
else
System.out.println("Does not contain Baz2");
}
}
打印出以下内容:
Foo
Bar
Baz2
Qux
Re-iterate:
Foo
Bar
Baz2
Qux
Contains Foo
Does not contain Baz
Does not contain Baz2
有趣的是,它知道Baz已经改变了;但它仍然没有找到Baz2。
显然这是设计的,但我正在看的非常合理的代码似乎(通过多个间接)导致了这个问题。至少使用Boost Multi Index,你必须使用一个迭代器来构造它!
答案 0 :(得分:3)
在Set
中使用可变对象(或Map
中的键)是不明智的。正如Set
的Javadoc所说:
注意:如果将可变对象用作set元素,则必须非常小心。如果在对象是集合中的元素的同时以影响等于比较的方式更改对象的值,则不指定集合的行为。
因此,您的示例直接在点,并将您的Set
放入“行为...未指定”区域。
根本原因正如Paul Bellora在回答中所说的那样。
答案 1 :(得分:1)
请注意LinkedHashSet
扩展HashSet
,它只包含只关心其键的HashMap
。所以我们在这里真正谈论的是HashMap
的行为。
HashMap
只保存对其键的引用,并且不负责跟踪何时对这些对象进行更改。它计算密钥哈希的唯一时间是put
或HashMap
自身调整大小。
理论上,跟踪其键的更改的自定义HashMap实现是可能的,但这取决于实现在其属性发生更改时触发通知的接口的键。这个自定义HashMap只能与特定类型的密钥一起使用的事实将使它非常专业。