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.