public class Rectangle implements Comparable<Rectangle> { private int width, height; public Rectangle(int width, int height); // getters and setters public int getWidth(); public int getHeight(); public void setWidth(int width); public void setHeight(int height); // identity public int hashCode(); public boolean equals(Object o); public int compareTo(Rectangle r); }
// add a rectangle to a hash set Set<Rectangle> s = new HashSet<Rectangle>(); Rectangle r = new Rectangle(2, 3); s.add(r); // s = [Rectangle(2, 3)] // mutate one of the rectangle's fields ... r.width = 5; // s = [Rectangle(5, 3)] s.size(); // 1 s.contains(r); // false // even more fun ... s.add(r); // s = [Rectangle(5, 3), Rectangle(5, 3)]
(a similar problem occurs with tree sets)
public class Square extends Rectangle { public Square(int length) { super(length, length); } }
public class Square extends Rectangle { public Square(int length) { super(length, length); } public void setLength(int length) { super.setWidth(); super.setHeight(); } @Override public void setWidth(int width) { setLength(width); } @Override public void setHeight(int height) { setLength(height); } }
@DataPoints public static void Rectangle[] rectangles(); // may contain squares! @Theory public void setWidthMustNotSetHeight(Rectangle r) { // get an arbitrary rectangle and remember its height int expectedHeight = r.getHeight(); // change the rectangle's width, and ... r.setWidth(0); assertEquals(r.getHeight(), expectedHeight); }
public class RectangleManager { private final Set<Rectangle> rectangles = new HashSet<Rectangle>(); public Set<Rectangle> getRectangles() { return rectangles; } }
public Set<Rectangle> getRectangles() { return Collections.unmodifiableSet(rectangles); // ugh }
public synchronized Set<Rectangle> getRectangles() { Set<Rectangle> copy = new HashSet<Rectangle>(); for (Rectangle r : rectangles) { copy.add(new Rectangle(r)); } return copy; }
public class Rectangle implements Comparable<Rectangle> { private final int width, height; public Rectangle(int width, int height); // getters public int getWidth(); public int getHeight(); // identity public int hashCode(); public boolean equals(Object o); public int compareTo(Rectangle r);
// setters public Rectangle setWidth(int width) { return new Rectangle(width, this.height); } public Rectangle setHeight(int height) { return new Rectangle(this.width, height); }
public class Stack<E> { public Stack<E> push(E element); // return a copy public Stack<E> pop(); // return a copy public E top(); public boolean isEmpty(); public static <E> Stack<E> empty(); // factory }
Stack<Integer> empty = Stack.empty();
Stack<Integer> a = empty.push(1);
Stack<Integer> b = a.push(2).push(3);
Stack<Integer> c = b.pop().push(4);
Stack<Integer> d = a.push(5);
b = b.pop(); // 3 can be GC'ed d = d.pop(); // 5 can be GC'ed
public class Stack<E> { private final E head; private final Stack<E> tail; private Stack() { head = null; tail = null; } private Stack(E head, Stack<E> tail) { this.head = head; this.tail = tail; }
public Stack<E> push(E element) { return new Stack<E>(element, this); } public Stack<E> pop() { return tail; } public E top() { return head; } public boolean isEmpty() { return tail == null; }
private static final Stack<Object> EMPTY = new Stack<Object>(); @SuppressWarnings("unchecked") public static <E> Stack<E> empty() { return (Stack<E>) EMPTY; }
public class Queue<E> { public Queue<E> enqueue(E element); // return a copy public Queue<E> dequeue(); // return a copy public E front(); public boolean isEmpty(); public static <E> Queue<E> empty(); // factory }
Queue<Integer> a = /* ... */;
Queue<Integer> b = a.dequeue();
Queue<Integer> b = a.dequeue();
Queue<Integer> b = a.dequeue();
Queue<Integer> empty = Queue.empty();
Queue<Integer> a = empty.enqueue(1);
Queue<Integer> b = a.enqueue(2).enqueue(3).enqueue(4);
Queue<Integer> c = b.dequeue();
Queue<Integer> d = c.dequeue().enqueue(5);
public Stack<E> reverse() { Stack<E> in = this, out = empty(); while (!in.isEmpty()) { out = out.push(in.head); in = in.tail; } return out; }
public class Queue<E> { private final Stack<E> in, out; private Queue() { in = Stack.empty(); out = Stack.empty(); } private Queue(Stack<E> in, Stack<E> out) { if (out.isEmpty()) { this.in = Stack.empty(); this.out = in.reverse(); } else { this.in = in; this.out = out; } }
public Queue<E> enqueue(E element) { return new Queue<E>(in.push(element), out); } public Queue<E> dequeue() { return new Queue<E>(in, out.pop()); } public E front() { return out.top(); } public boolean isEmpty() { return out.isEmpty(); }
private static final Queue<Object> EMPTY = new Queue<Object>(); @SuppressWarnings("unchecked") public static <E> Queue<E> empty() { return (Queue<E>) EMPTY; }
Adapt for concurrent reads and writes
public class CASQueue<E> { private final AtomicReference<Queue<E>> q; private final Semaphore nonEmpty = new Semaphore(0); public CASQueue(Queue<E> q) { this.q = new AtomicReference<Queue<E>>(q); } // compare-and-set loop public void enqueue(E element); public E dequeue(); }
public class LockQueue<E> { private Queue<E> q; private final Lock lock = new ReentrantLock(); private final Condition nonEmpty = lock.newCondition(); public LockQueue(Queue<E> q) { this.q = q; } // lock/unlock, await/signal public void enqueue(E element); public E dequeue(); }
Queue<Integer> empty = Queue.empty();
Queue<Integer> a = empty.enqueue(1);
Queue<Integer> b = a.enqueue(2).enqueue(3);
Queue<Integer> c = b.enqueue(4);
Queue<Integer> d = c.enqueue(5)./* ... */.enqueue(10);
Queue<Integer> e = c.dequeue();
public int size() { return isEmpty() ? 0 : tail.size() + 1; }
public class Queue<E> { private final Stack<E> in; private final Queue<Stack<E>> spine; private final Stack<E> out; @SuppressWarnings("unchecked") private Queue() { in = Stack.empty(); spine = (Queue<Stack<E>>) this; out = Stack.empty(); }
private Queue(Stack<E> in, Queue<Stack<E>> spine, Stack<E> out) { if (out.isEmpty()) { if (spine.isEmpty()) { // case 1: same as naïve queue this.in = Stack.empty(); this.spine = empty(); this.out = in.reverse(); } else { // case 2: dequeue spine this.in = in; this.spine = spine.dequeue(); this.out = spine.front(); } } else { if (in.size() == 3) { // case 3: enqueue spine this.in = Stack.empty(); this.spine = spine.enqueue(in.reverse()); this.out = out; } else { // case 4: same as naïve queue this.in = in; this.spine = spine; this.out = out; } } }
public Queue<E> enqueue(E element) { return new Queue<E>(in.push(element), spine, out); } public Queue<E> dequeue() { return new Queue<E>(in, spine, out.pop()); } public E front() { return out.top(); } public boolean isEmpty() { return out.isEmpty(); }
private static final Queue<Object> EMPTY = new Queue<Object>(); @SuppressWarnings("unchecked") public static <E> Queue<E> empty() { return (Queue<E>) EMPTY; }
Solved, trivially.
Not solved, but making state change visible (by returning copies) helps.
Solved efficiently with persistent data structures.