我不认为这在一般意义上的Java中可以可靠地完成。没有一般的机制来确保对物体突变的某种作用。
有几种解决方案方法可能足以满足您的使用案例。
1. 观察元素的变化
- 您需要控制进入集合的类型实现
- 每当集合中的对象更新时,性能成本小
您可以尝试强制实施一个像构造这样的观察者,其中 Set 类被注册为其所有项的观察者。这意味着您需要控制可以放入 Set 中的对象类型(仅限可观察对象)。此外,您需要确保 Observables 会通知观察者可能影响哈希码和等于的每个变化。我不知道已经存在任何像这样的类。就像Ray在下面提到的,你还需要注意潜在的并发问题。例:
package collectiontests.observer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
public class ChangeDetectingSet<E extends Observable> implements Set<E>, Observer {
private HashSet<E> innerSet;
public void update(Observable o, Object arg) {
innerSet.remove(o);
innerSet.add((E)o);
}
public int size() {
return innerSet.size();
}
public boolean isEmpty() {
return innerSet.isEmpty();
}
public boolean contains(Object o) {
return innerSet.contains(o);
}
public Iterator<E> iterator() {
return innerSet.iterator();
}
public Object[] toArray() {
return innerSet.toArray();
}
public <T> T[] toArray(T[] a) {
return innerSet.toArray(a);
}
public boolean add(E e) {
e.addObserver(this);
return innerSet.add(e);
}
public boolean remove(Object o) {
if(o instanceof Observable){
((Observable) o).deleteObserver(this);
}
return innerSet.remove(o);
}
public boolean containsAll(Collection<?> c) {
return innerSet.containsAll(c);
}
public boolean addAll(Collection<? extends E> c) {
boolean result = false;
for(E el: c){
result = result || add(el);
}
return result;
}
public boolean retainAll(Collection<?> c) {
Iterator<E> it = innerSet.iterator();
E el;
Collection<E> elementsToRemove = new ArrayList<E>();
while(it.hasNext()){
el = it.next();
if(!c.contains(el)){
elementsToRemove.add(el); //No changing the set while the iterator is going. Iterator.remove may not do what we want.
}
}
for(E e: elementsToRemove){
remove(e);
}
return !elementsToRemove.isEmpty(); //If it's empty there is no change and we should return false
}
public boolean removeAll(Collection<?> c) {
boolean result = false;
for(Object e: c){
result = result || remove(e);
}
return result;
}
public void clear() {
Iterator<E> it = innerSet.iterator();
E el;
while(it.hasNext()){
el = it.next();
el.deleteObserver(this);
}
innerSet.clear();
}
}
每次可变对象发生更改时,这都会降低性能。
2. 使用 Set 时检查更改
- 适用于要放入集合中的任何现有对象
- 每次需要有关集合的信息时都需要扫描整个集合(如果集合变得非常大,则性能开销可能会变得很大)。
如果集合中的对象经常更改,但集合本身很少使用,则可以尝试下面的Joe解决方案。他建议每当您调用 Set 的方法时,检查 Set 是否仍然正确。作为奖励,他的方法将适用于任何一组对象(不必将其限制为可观察量)。在性能方面,他的方法对于大型集合或常用的集合来说是有问题的(因为每次方法调用都需要检查整个集合)。
Joe方法的可能实现:
package collectiontests.check;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class ListBasedSet<E> {
private List<E> innerList;
public ListBasedSet(){
this(null);
}
public ListBasedSet(Collection<E> elements){
if (elements != null){
innerList = new ArrayList<E>(elements);
} else {
innerList = new ArrayList<E>();
}
}
public void add(E e){
innerList.add(e);
}
public int size(){
return toSet().size();
}
public Iterator<E> iterator(){
return toSet().iterator();
}
public void remove(E e){
while(innerList.remove(e)); //Keep removing until they are all gone (so set behavior is kept)
}
public boolean contains(E e){
//I think you could just do innerList.contains here as it shouldn't care about duplicates
return innerList.contains(e);
}
private Set<E> toSet(){
return new HashSet<E>(innerList);
}
}
以及 check always 方法的另一个实现(这个基于现有集)。如果您想尽可能多地重用现有集,这就是要走的路。
package collectiontests.check;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NavigableSet;
import java.util.SortedSet;
import java.util.TreeSet;
public class ChangeDetectingSet<E> extends TreeSet<E> {
private boolean compacting = false;
@SuppressWarnings("unchecked")
private void compact(){
//To avoid infinite loops, make sure we are not already compacting (compact also gets called in the methods used here)
if(!compacting){ //Warning: this is not thread-safe
compacting = true;
Object[] elements = toArray();
clear();
for(Object element: elements){
add((E)element); //Yes unsafe cast, but we're rather sure
}
compacting = false;
}
}
@Override
public boolean add(E e) {
compact();
return super.add(e);
}
@Override
public Iterator<E> iterator() {
compact();
return super.iterator();
}
@Override
public Iterator<E> descendingIterator() {
compact();
return super.descendingIterator();
}
@Override
public NavigableSet<E> descendingSet() {
compact();
return super.descendingSet();
}
@Override
public int size() {
compact();
return super.size();
}
@Override
public boolean isEmpty() {
compact();
return super.isEmpty();
}
@Override
public boolean contains(Object o) {
compact();
return super.contains(o);
}
@Override
public boolean remove(Object o) {
compact();
return super.remove(o);
}
@Override
public void clear() {
compact();
super.clear();
}
@Override
public boolean addAll(Collection<? extends E> c) {
compact();
return super.addAll(c);
}
@Override
public NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) {
compact();
return super.subSet(fromElement, fromInclusive, toElement, toInclusive);
}
@Override
public NavigableSet<E> headSet(E toElement, boolean inclusive) {
compact();
return super.headSet(toElement, inclusive);
}
@Override
public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
compact();
return super.tailSet(fromElement, inclusive);
}
@Override
public SortedSet<E> subSet(E fromElement, E toElement) {
compact();
return super.subSet(fromElement, toElement);
}
@Override
public SortedSet<E> headSet(E toElement) {
compact();
return super.headSet(toElement);
}
@Override
public SortedSet<E> tailSet(E fromElement) {
compact();
return super.tailSet(fromElement);
}
@Override
public Comparator<? super E> comparator() {
compact();
return super.comparator();
}
@Override
public E first() {
compact();
return super.first();
}
@Override
public E last() {
compact();
return super.last();
}
@Override
public E lower(E e) {
compact();
return super.lower(e);
}
@Override
public E floor(E e) {
compact();
return super.floor(e);
}
@Override
public E ceiling(E e) {
compact();
return super.ceiling(e);
}
@Override
public E higher(E e) {
compact();
return super.higher(e);
}
@Override
public E pollFirst() {
compact();
return super.pollFirst();
}
@Override
public E pollLast() {
compact();
return super.pollLast();
}
@Override
public boolean removeAll(Collection<?> c) {
compact();
return super.removeAll(c);
}
@Override
public Object[] toArray() {
compact();
return super.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
compact();
return super.toArray(a);
}
@Override
public boolean containsAll(Collection<?> c) {
compact();
return super.containsAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
compact();
return super.retainAll(c);
}
@Override
public String toString() {
compact();
return super.toString();
}
}
3. 使用 Scala 集
你可以作弊并取消可变对象(从某种意义上说,你不会改变,而是创建一个属性更改的新对象)。你可以看看Scala中的集合(我认为可以从Java调用Scala,但我不是100%确定):http://www.scala-lang.org/api/current/scala/collection/immutable/IndexedSeq.html