Я могу понять, почему классы параллелизма трудны для написания: там очень легко получить трудно видимые ошибки.
В Java есть хороший способ избежать таких ошибок при написании неизменяемых Collection
классов: каждый тип Collection
имеет метод, подобный java.util.Collections.unmodifiableSet(someSet)
, который даст вам обертку, которая позволит вам увидеть базовый Collection
, но заблокирует все методы мутации.Однако это всего лишь обертка: вы все равно можете изменить базовый Collection
, если сохраните ссылку на него, так что не делайте этого.Кроме того, немедленно клонируйте и оберните любые Collection
s, которые приходят из-за вашего контроля, так как программисты, которые передали их вам, могут изменить их позже, изменяя ваши хорошие неизменяемые данные.Если вы хотите создать библиотеку для всех этих педантичных мер предосторожности, это отнимает много времени, но не так сложно.Чтобы сэкономить ваше время, я включил пример минимально оптимизированной FunctionalHashSet
со всеми необходимыми средствами предотвращения мутаций.
Я создал абстрактный суперкласс, опустив список API для Set
(не забывая toString
).Для немутирующих методов я просто передаю их базовому Set
.Для мутационных методов я выбрасываю UnsupportedOperationException
и предоставляю альтернативные методы функционального стиля.
Вот этот абстрактный класс, FunctionalSet
:
import java.util.Collections;
import java.util.Collection;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
public abstract class FunctionalSet<E> implements Set<E> {
// final to prevent mutations through reassignment.
protected final Set<E> set;
// private to prevent any use of the default constructor.
private FunctionalSet()
{ this.set = null; }
// unmodifiableSet to prevent mutations through Iterator and in subclasses.
protected FunctionalSet(final Set<E> set)
{ this.set = Collections.unmodifiableSet(set); }
public abstract FunctionalSet<E> clone();
public abstract FunctionalSet<E> fAdd(final E element);
public abstract FunctionalSet<E> fAddAll(final Collection<? extends E> elements);
public abstract FunctionalSet<E> fRemove(final Object element);
public abstract FunctionalSet<E> fRemoveAll(final Collection<?> elements);
public abstract FunctionalSet<E> fRetainAll(final Collection<?> elements);
protected abstract FunctionalSet<E> newFSet(final Set<E> newSet);
protected abstract Set<E> newSet();
protected abstract Set<E> cloneSet();
protected final FunctionalSet<E> __fAdd(final E element) {
if (set.contains(element)) return this;
final Set<E> newSet = cloneSet();
newSet.add(element);
return newFSet(newSet);
}
protected final FunctionalSet<E> __fAddAll(final Collection<? extends E> elements) {
if (set.containsAll(elements)) return this;
final Set<E> newSet = cloneSet();
newSet.addAll(elements);
return newFSet(newSet);
}
protected final FunctionalSet<E> __fRemove(final Object element) {
if (!set.contains(element)) return this;
final Set<E> newSet = cloneSet();
newSet.remove(element);
return newFSet(newSet);
}
protected final Set<E> __fRemoveAll(final Collection<?> elements) {
boolean hasNone = true;
for (final Object element : elements) {
if (set.contains(element)) {
hasNone = false;
break;
}
}
if (hasNone) return this;
final Set<E> newSet = cloneSet();
newSet.removeAll(elements);
return newFSet(newSet);
}
@SuppressWarnings("unchecked")
protected final Set<E> __fRetainAll(final Collection<?> rawElements) {
final Set elements = rawElements instanceof Set ? (Set) rawElements : new HashSet(rawElements);
// If set is a subset of elements, we don't remove any of the elements.
if (set.size() <= elements.size() && elements.containsAll(set)) return this;
final Set<E> newSet = newSet();
for (final E element : set) {
if (elements.contains(element)) newSet.add(element);
}
return newFSet(newSet);
}
private final UnsupportedOperationException unsupported(final String call, final String goodCall) {
return new UnsupportedOperationException(
String.format(this.getClass().getName() + "s are immutable. Use %s instead of %s.", goodCall, call)
);
}
public final boolean add(final E element)
{ throw unsupported("add", "fAdd"); }
public final boolean addAll(final Collection<? extends E> elements)
{ throw unsupported("addAll", "fAddAll"); }
public final void clear()
{ throw unsupported("clear", "new " + this.getClass().getName() + "()"); }
public final boolean remove(final Object element)
{ throw unsupported("remove", "fRemove"); }
public final boolean removeAll(final Collection<?> elements)
{ throw unsupported("removeAll", "fRemoveAll"); }
public final boolean retainAll(final Collection<?> elements)
{ throw unsupported("retainAll", "fRetainAll"); }
public final boolean contains(final Object element)
{ return set.contains(element); }
public final boolean containsAll(final Collection<?> elements)
{ return set.containsAll(elements); }
public final boolean equals(final Object object)
{ return set.equals(object); }
public final int hashCode()
{ return set.hashCode(); }
public final boolean isEmpty()
{ return set.isEmpty(); }
public final Iterator<E> iterator()
{ return set.iterator(); }
public final int size()
{ return set.size(); }
public final Object[] toArray()
{ return set.toArray(); }
public final <E> E[] toArray(final E[] irrelevant)
{ return set.toArray(irrelevant); }
public final String toString()
{ return set.toString(); }
}
В реализации очень мало осталосьделать.Я предоставляю несколько конструкторов и служебных методов и просто использую реализации по умолчанию всех методов мутации.
Вот реализация, FunctionalHashSet
.:
import java.util.Collection;
import java.util.Set;
import java.util.HashSet;
public final class FunctionalHashSet<E> extends FunctionalSet<E> implements Cloneable {
public static final FunctionalHashSet EMPTY = new FunctionalHashSet();
public FunctionalHashSet()
{ super(new HashSet<E>()); }
public FunctionalHashSet(final HashSet<E> set)
{ this(set, true); }
@SuppressWarnings("unchecked")
private FunctionalHashSet(final HashSet<E> set, final boolean clone)
{ super(clone ? (HashSet<E>) set.clone() : set); }
public FunctionalHashSet(final Collection<E> elements)
{ this(new HashSet<E>(elements)); }
protected FunctionalHashSet<E> newFSet(final Set<E> newSet)
{ return new FunctionalHashSet<E>((HashSet<E>) newSet, false); }
protected HashSet<E> newSet()
{ return new HashSet<E>(); }
@SuppressWarnings("unchecked")
protected HashSet<E> cloneSet()
{ return new HashSet<E>(set); }
public FunctionalHashSet<E> clone()
{ return this; }
public FunctionalHashSet<E> fAdd(final E element)
{ return (FunctionalHashSet<E>) __fAdd(element); }
public FunctionalHashSet<E> fAddAll(final Collection<? extends E> elements)
{ return (FunctionalHashSet<E>) __fAddAll(elements); }
public FunctionalHashSet<E> fRemove(final Object element)
{ return (FunctionalHashSet<E>) __fRemove(element); }
public FunctionalHashSet<E> fRemoveAll(final Collection<?> elements)
{ return (FunctionalHashSet<E>) __fRemoveAll(elements); }
public FunctionalHashSet<E> fRetainAll(final Collection<?> elements)
{ return (FunctionalHashSet<E>) __fRetainAll(elements); }
}
Несколько замечаний:
- В каждом методе функциональных мутаций я проверяю, действительно ли произойдут какие-либо изменения.Если нет, я просто возвращаю точно такой же
FunctionalSet
. - В
clone
я просто возвращаю точно такое же FunctionalSet
- Запуск от
set
до java.util.Collections.unmodifiableSet
и объявление его final
предотвращает два источника мутации: пользователи не могут мутировать черезIterator
и разработчики не могут случайно мутировать в своих реализациях.
Вы можете воспользоваться этим и немного изменить его для поддержки других Collection
s.