Ответ 1
Я не думаю, что это можно надежно сделать на Java в общем смысле. Нет общего механизма для обеспечения определенного действия на мутацию объекта.
Существует несколько подходов к решениям, которые могут быть достаточными для вашего использования.
1. Наблюдайте за элементами изменений
- Вам нужно иметь контроль над реализацией типов, которые входят в набор
- Небольшая производительность при каждом обновлении объекта в вашем наборе обновлений.
Вы можете попытаться выполнить observer, как конструкция, где ваш класс Set зарегистрирован как Observer во все его элементы. Это означает, что вам нужно будет контролировать типы объектов, которые можно поместить в объекты Set (только Observable). Кроме того, вам необходимо убедиться, что Observables уведомит наблюдателя о каждом изменении, которое может повлиять на hashcode и равно. Я не знаю такого класса, который уже существует. Как упоминает Рэй ниже, вам нужно также следить за потенциальными проблемами concurrency. Пример:
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 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 все равно, когда вы вызываете метод на нем. В качестве бонуса его метод будет работать над любым набором объектов (без ограничения его на наблюдаемые). По его мнению, его метод будет проблематичным для больших наборов или часто используемых наборов (так как весь набор необходимо проверять при каждом вызове метода).
Возможная реализация метода Джо:
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);
}
}
И еще одна реализация метода проверки всегда (эта основана на существующем наборе). Это путь, если вы хотите как можно больше использовать существующие наборы.
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 (я думал, что можно вызвать Scala из Java, но я не уверен на 100%): < а3 >