The Set interface extends the Collection interface, but adds no new methods:
interface Set<T>
extends Collection<T> { }
Instead, restrictions are placed on the implementations of the methods. For example, the add method, if it's implemented, ignores attempts to add duplicate elements.
A set uses the equals predicate of its elements to determine if two elements are the same (i.e., logical equality).
Sorted sets are sets whose elements must be comparable. In this case elements are placed in the set according to their order.
HashSet<T> (backed by open hash tables) and LinkedHashSet<T> (backed by open hash tables with linked elements) implement Set.
TreeSet<T> (backed by red-black trees) implement SortedSet.
Study the following canonical class declaration:
class Cell implements Comparable {
private int val;
public Cell(int val) { this.val = val;
}
public Cell() { this(0); }
public String toString() {
return "Cell[" + val +
"]";
}
public int getVal() { return val; }
public boolean equals(Object o) {
if (o == null) return false;
Class c1 = getClass();
Class c2 = o.getClass();
if (!c1.equals(c2)) return false;
Cell other = (Cell)o;
return val == other.val;
}
public int hashCode() {
return toString().hashCode();
}
public int compareTo(Object o) {
if (o == null) throw new
ClassCastException();
Class c1 = getClass();
Class c2 = o.getClass();
if (!c1.equals(c2)) throw new ClassCastException();
Cell other = (Cell)o;
if (equals(other)) return 0;
if (val < other.val) return -1;
else return 1;
}
}
In addition, we declare a comparator for Cell:
class CellComparator implements Comparator {
public int compare(Object o1, Object
o2) {
if (o1 == null) {
if (o2 == null) return 0;
throw new ClassCastException();
}
Class c1 = o1.getClass();
Class c2 = o2.getClass();
Class c3 = (new Cell()).getClass();
if (!c1.equals(c2) ||
!c1.equals(c3)) {
throw new ClassCastException();
}
int arg1 = ((Cell)o1).getVal();
int arg2 = ((Cell)o2).getVal();
if (arg1 < arg2) return 1;
if (arg2 < arg1) return -1;
return 0;
}
public boolean equals(Object o) {
return o == this; // for now
}
}
We create four sets of cells:
Set<Cell> set1 = new HashSet<Cell>();
Set<Cell> set2 = new LinkedHashSet<Cell>();
SortedSet<Cell> set3 = new TreeSet<Cell>();
SortedSet<Cell> set4 = new TreeSet<Cell>(new CellComparator());
Let's create some cells:
Cell[] cell = new Cell[4];
cell[0] = new Cell(22);
cell[1] = new Cell(3);
cell[2] = new Cell(42);
cell[3] = new Cell(-1);
Next, we add these cells to our sets:
for(int i = 0; i < 4; i++) {
set1.add(cell[i]);
set2.add(cell[i]);
set3.add(cell[i]);
set4.add(cell[i]);
}
Displaying the sets:
System.out.println(set1);
System.out.println(set2);
System.out.println(set3);
System.out.println(set4);
produces the output:
[Cell[22], Cell[42], Cell[3], Cell[-1]]
[Cell[22], Cell[3], Cell[42], Cell[-1]]
[Cell[-1], Cell[3], Cell[22], Cell[42]]
[Cell[42], Cell[22], Cell[3], Cell[-1]]
Notice that the order of elements in set3 is determined by their natural order, i.e., the order given by the compareTo method. The order of elements in set4 is determined by the comaparator passed to the constructor, which is the reverse of the natural order. The order of elements in set2 is the insertion order. Finally, the order of elements in set1 is determined by the hash codes.
Sets ignore attempts to add duplicate elements. For example, executing the commands:
set1.add(new Cell(new Cell(3));
System.out.println(set1);
produces the output:
[Cell[22], Cell[42], Cell[3], Cell[-1]]
Notice that cells are not mutable. Assume we provide a setVal() method:
class Cell implements Comparable {
private int val;
public void setVal(int val) { this.val
= val; }
// etc.
}
After we add cells to our sets, we mutate cell[3]'s value from –1 to 500:
cell[3].setVal(500);
Notice that the order of the sets no longer follows the rules described earlier:
[Cell[22], Cell[42], Cell[3], Cell[500]]
[Cell[22], Cell[3], Cell[42], Cell[500]]
[Cell[500], Cell[3], Cell[22], Cell[42]]
[Cell[42], Cell[22], Cell[3], Cell[500]]