通过包装LinkedHashSet实现IdentityLinkedHashSet

时间:2019-01-18 12:43:23

标签: java hashset

在Java标准库中,LinkedHashSetHashSet相同,但是具有可预测的迭代顺序(插入顺序)。我需要一个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!

在此实施中我做错了什么?

1 个答案:

答案 0 :(得分:2)

调用IdentityWrapper.equals()方法时。 LinkedHashSet不会通过ELEM,而是通过IdentityWrapper对象。

您必须引入额外的检查(instanceOf),然后解开传递的对象以比较元素:

public boolean equals(Object obj) {
    return (obj instanceof IdentityLinkedHashSet<?>.IdentityWrapper) && 
        ELEM == ((IdentityLinkedHashSet<?>.IdentityWrapper) obj).ELEM;
}

一些注意事项:

  1. instanceof将检查是否为空,因此您可以完全删除该检查。
  2. 您必须像这样引用IdentityWrapperIdentityLinkedHashSet<?>.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);
        }
    }