创建一个使用自定义条件而不是等于的HashSet

时间:2019-08-06 19:42:15

标签: java equals hashset

这是我正在使用的类的结构:

public class Foo {
    private final Bar bar; //Bar is immutable
    private int state;

    public Foo(Bar bar, int state) {
        this.bar = bar;
        this.state = state;
    }

    public Bar getBar() {
        return bar;
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }

    @Override
    public int hashCode() {
        return Objects.hash(bar, state);
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Foo)) return false;
        Foo other = (Foo) obj;
        return Objects.equals(bar, other.getBar()) && state == other.getState();
    }
}

我遇到的问题如下:

Bar bar = ...;
Foo foo = new Foo(bar, 0);
Set<Foo> set = new HashSet<>();
set.add(foo);
foo.setState(1);
set.remove(foo); //foo is not removed!

没有将元素foo从集合中删除,因为元素的哈希码由于foo.setState(1)而改变,因此哈希集无法找到它。我想要的功能是让HashSet<Foo>组织其哈希存储桶并使用foo.getBar()检查是否相等。遵循以下内容:

Bar bar = ...;
Foo foo = new Foo(bar, 0);
Set<Foo> set = new HashSet<>(Foo::getBar);
set.add(foo);
foo.setState(1);
set.remove(foo); //foo is removed since oldFoo.getBar().hashCode() == newFoo.getBar().hashCode() && Objects.equals(oldFoo.getBar(), newFoo.getBar())

为此,我提出了以下课程:

public class KeyExtractedHashSet<T, K> extends AbstractSet<T> implements Set<T> {
    private final Map<K, T> map;
    private final Function<? super T, ? extends K> keyExtractor;

    public KeyExtractedHashSet(Function<? super T, ? extends K> keyExtractor) {
        map = new HashMap<>();
        this.keyExtractor = keyExtractor;
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public Iterator<T> iterator() {
        return map.values().iterator();
    }

    @Override
    public boolean add(T t) {
        return map.put(keyExtractor.apply(t), t) == null;
    }
}

这种方法是否有潜在的问题?

1 个答案:

答案 0 :(得分:1)

实施自己的Set实现是一项艰巨的任务,容易出错或效率较低。
例如,HashSet覆盖并优化在父类(AbstractCollectionAbstractSet)中实现的多个方法。

例如contains(Object o)怎么样?

您从AbstractCollection中定义的方法继承,该方法具有O(n)时间复杂度,这是正常的,因为抽象集合没有允许更快地迭代其元素的抽象级别:

public boolean contains(Object o) {
    Iterator<E> it = iterator();
    if (o==null) {
        while (it.hasNext())
            if (it.next()==null)
                return true;
    } else {
        while (it.hasNext())
            if (o.equals(it.next()))
                return true;
    }
    return false;
}

您可以将地图作为HashSet的O(1)时间复杂度实现来实现:

public boolean contains(Object o) {
   return map.containsKey(o)   
}

但是由于foo元素存储在映射的值中,因此您应该这样做:

public boolean contains(Object o) {
   return map.containsValue(o);   
}

但是由于Foo.equals()考虑了您要忽略的state字段,因此无法正常工作。此外,map.containsValue(o)也不是O(1),因为它可能会在地图的所有元素上进行迭代。

remove(Object o)相同。

再举一个例子,想象一下:

Set<Foo> foos = new KeyExtractedHashSet<Foo, Bar>();
// populate, remove...

然后一种方法从Set创建一个新的foos

Set<Foo> someFoos = foos.stream().filter(...).toSet();

someFoos不再是KeyExtractedHashSet。它很容易被遗忘。

长话短说:您应该重新考虑设计,并建议将不可变的对象用作HashSet的键。

相关问题