这是我正在使用的类的结构:
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;
}
}
这种方法是否有潜在的问题?
答案 0 :(得分:1)
实施自己的Set实现是一项艰巨的任务,容易出错或效率较低。
例如,HashSet
覆盖并优化在父类(AbstractCollection
和AbstractSet
)中实现的多个方法。
例如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
的键。