I can understand why the concurrency classes are difficult to write: it's very easy to get difficult-to-see bugs there.
Java has a nice way to avoid such bugs when writing immutable Collection classes: each kind of Collection has a method similar to java.util.Collections.unmodifiableSet(someSet), which will give you a wrapper that lets you see the underlying Collection but blocks all mutation methods. It's only a wrapper, though: you can still change the underlying Collection if you keep a reference to it lying around, so don't do that. Also, immediately clone and wrap any Collections that come from outside your control, since the programmers who handed them to you might modify them later, mutating your nice immutable data.
If you want to make a library to handle all these pedantic precautions for you, it's time-consuming but not that hard. To save you time, I've included an example of a minimally-optimized
FunctionalHashSet with all necessary mutation prevention.
I made an abstract superclass by going down the API listing for Set (not forgetting toString). For nonmutating methods, I simply pass them on to the underlying Set. For mutating methods, I throw UnsupportedOperationException and provide alternative functional-style methods.
Here's that abstract class, 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 implements Set {
// final to prevent mutations through reassignment.
protected final Set 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 set)
{ this.set = Collections.unmodifiableSet(set); }
public abstract FunctionalSet clone();
public abstract FunctionalSet fAdd(final E element);
public abstract FunctionalSet fAddAll(final Collection extends E> elements);
public abstract FunctionalSet fRemove(final Object element);
public abstract FunctionalSet fRemoveAll(final Collection> elements);
public abstract FunctionalSet fRetainAll(final Collection> elements);
protected abstract FunctionalSet newFSet(final Set newSet);
protected abstract Set newSet();
protected abstract Set cloneSet();
protected final FunctionalSet __fAdd(final E element) {
if (set.contains(element)) return this;
final Set newSet = cloneSet();
newSet.add(element);
return newFSet(newSet);
}
protected final FunctionalSet __fAddAll(final Collection extends E> elements) {
if (set.containsAll(elements)) return this;
final Set newSet = cloneSet();
newSet.addAll(elements);
return newFSet(newSet);
}
protected final FunctionalSet __fRemove(final Object element) {
if (!set.contains(element)) return this;
final Set newSet = cloneSet();
newSet.remove(element);
return newFSet(newSet);
}
protected final Set __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 newSet = cloneSet();
newSet.removeAll(elements);
return newFSet(newSet);
}
@SuppressWarnings("unchecked")
protected final Set __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 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 iterator()
{ return set.iterator(); }
public final int size()
{ return set.size(); }
public final Object[] toArray()
{ return set.toArray(); }
public final E[] toArray(final E[] irrelevant)
{ return set.toArray(irrelevant); }
public final String toString()
{ return set.toString(); }
}
In the implementation, there's very little left to do. I supply a few constructors and utility methods and simply use the default implementations of all the mutating methods.
Here's an implementation, FunctionalHashSet. :
import java.util.Collection;
import java.util.Set;
import java.util.HashSet;
public final class FunctionalHashSet extends FunctionalSet implements Cloneable {
public static final FunctionalHashSet EMPTY = new FunctionalHashSet();
public FunctionalHashSet()
{ super(new HashSet()); }
public FunctionalHashSet(final HashSet set)
{ this(set, true); }
@SuppressWarnings("unchecked")
private FunctionalHashSet(final HashSet set, final boolean clone)
{ super(clone ? (HashSet) set.clone() : set); }
public FunctionalHashSet(final Collection elements)
{ this(new HashSet(elements)); }
protected FunctionalHashSet newFSet(final Set newSet)
{ return new FunctionalHashSet((HashSet) newSet, false); }
protected HashSet newSet()
{ return new HashSet(); }
@SuppressWarnings("unchecked")
protected HashSet cloneSet()
{ return new HashSet(set); }
public FunctionalHashSet clone()
{ return this; }
public FunctionalHashSet fAdd(final E element)
{ return (FunctionalHashSet) __fAdd(element); }
public FunctionalHashSet fAddAll(final Collection extends E> elements)
{ return (FunctionalHashSet) __fAddAll(elements); }
public FunctionalHashSet fRemove(final Object element)
{ return (FunctionalHashSet) __fRemove(element); }
public FunctionalHashSet fRemoveAll(final Collection> elements)
{ return (FunctionalHashSet) __fRemoveAll(elements); }
public FunctionalHashSet fRetainAll(final Collection> elements)
{ return (FunctionalHashSet) __fRetainAll(elements); }
}
A few notes :
- In every single functional mutation method, I check whether there will actually be any changes. If not, I just return the exact same
FunctionalSet.
- In
clone, I just return the exact same FunctionalSet
- Running
set through java.util.Collections.unmodifiableSet and declaring it final prevents two sources of mutation: users can't mutate through the Iterator and implementors can't accidentally mutate in their implementations.
You can take this and modify it a bit to support other Collections.