在Java标准库中,LinkedHashSet
与HashSet
相同,但是具有可预测的迭代顺序(插入顺序)。我需要一个IdentityHashSet
(它使用对象身份或引用相等,而不是对象相等)并具有可预测的迭代顺序(插入顺序)。
尽管Java的标准库中没有IdentityHashSet
,但您可以使用可用的IdentityHashMap
通过以下语句轻松创建一个库:
Set<T> identitySet = java.util.Collections.newSetFromMap(new IdentityHashMap<>());
这几乎与Guava的Sets.newIdentityHashSet()
中使用的实现相同;但这具有HashMap
键(和HashSet
元素)的不确定的,通常混乱的顺序。
我搜索IdentityLinkedHashSet
的实现没有结果,所以我决定自己实现。我发现一个有用的结果是this answer,该结果建议在与LinkedHashSet
的组合中使用身份包装器类。
我试图实现这个想法。以下是我的实现的一些关键代码段。 Access the full Gist here。
public class IdentityLinkedHashSet<E> implements Set<E> {
private LinkedHashSet<IdentityWrapper> set;
/* ... constructors ... */
@Override
public boolean add(E e) {
return set.add(new IdentityWrapper(e));
}
@Override
public boolean contains(Object obj) {
return set.contains(new IdentityWrapper((E) obj));
}
/* ... rest of Set methods ... */
private class IdentityWrapper {
public final E ELEM;
IdentityWrapper(E elem) {
this.ELEM = elem;
}
@Override
public boolean equals(Object obj) {
return obj != null && ELEM == obj;
}
@Override
public int hashCode() {
return System.identityHashCode(ELEM);
}
}
}
然后,我编写了一些单元测试来验证我的实现。不幸的是,某些断言失败了!这是我的测试:
String str1 = new String("test-1");
String str2 = new String("test-2");
String str3 = new String("test-2");
Set<String> identitySet = new IdentityLinkedHashSet<>();
assertTrue(idSet.add(str1));
assertFalse(idSet.add(str1)); // <-- fails!
assertTrue(idSet.contains(str1)); // <-- fails!
//
assertTrue(idSet.add(str2));
assertFalse(idSet.add(str2)); // <-- fails!
assertTrue(idSet.contains(str2)); // <-- fails!
assertFalse(idSet.contains(str3));
//
assertTrue(idSet.add(str3));
assertFalse(idSet.add(str3)); // <-- fails!
assertTrue(idSet.contains(str3)); // <-- fails!
assertEquals(3, idSet.size()); // <-- fails!
在此实施中我做错了什么?
答案 0 :(得分:2)
调用IdentityWrapper.equals()
方法时。 LinkedHashSet
不会通过ELEM
,而是通过IdentityWrapper
对象。
您必须引入额外的检查(instanceOf
),然后解开传递的对象以比较元素:
public boolean equals(Object obj) {
return (obj instanceof IdentityLinkedHashSet<?>.IdentityWrapper) &&
ELEM == ((IdentityLinkedHashSet<?>.IdentityWrapper) obj).ELEM;
}
一些注意事项:
instanceof
将检查是否为空,因此您可以完全删除该检查。 您必须像这样引用IdentityWrapper
:IdentityLinkedHashSet<?>.IdentityWrapper
,因为它不是static
类。如评论中所述。可以将其设置为静态,并且ELEM
的类型可以从E
更改为Object
。您还会使用更好的equals
方法离开哪一个?
private static class IdentityWrapper {
public final Object ELEM;
IdentityWrapper(Object elem) {
this.ELEM = elem;
}
@Override
public boolean equals(Object obj) {
return (obj instanceof IdentityWrapper) && ELEM == ((IdentityWrapper) obj).ELEM;
}
@Override
public int hashCode() {
return System.identityHashCode(ELEM);
}
}