A Set of Coordinate instances

exercise No. 191

Inserting Coordinate instances into a Set.

Q:

Create a Set of Coordinate instances including duplicates. Iterate over this set and write each value to System.out in a similar fashion to Inserting strings into a Set. . Compare your output to the corresponding example Inserting strings into a Set. .

Did you expect this behaviour? Explain this result and a solution.

A:

Our code is very similar to Inserting strings into a Set. :

final Set<Coordinate>  points =  new HashSet<Coordinate>();

points.add(new Coordinate(1,  2));
points.add(new Coordinate(4,  1));
points.add(new Coordinate(1,  2)); // Equal to first Object

// Iterate over all inserted coordinates
System.out.println("The set contains " + points.size() + " elements:");
for (final Coordinate c : points) {
  System.out.println(c.toString());
}

Since we do have set semantics we expect the duplicate coordinate value (1|2) to be dropped and thus to appear only once. So our set should contain {(4|1), (1|2)}. We however see the duplicate object appearing on standard output:

The set contains 3 elements:
(4|1)
(1|2)
(1|2)

This is due to our own fault not providing a hashCode() implementation being compatible to our overridden equals() method. Consider:

final Coordinate
  c12 = new Coordinate(1, 2),
  c12Duplicate = new Coordinate(1, 2);

System.out.println("c12.hashCode() and c12Duplicate.hashCode():"+
  c12.hashCode() + "," + c12Duplicate.hashCode());

This yields the following output:

c12.hashCode() and c12Duplicate.hashCode():1334574952,1882008996

Apparently the two instances c12 and c12Duplicate are equal to each other. Their hash codes however are different clearly violating the contract being described in Java Collections - hashCode() and equals(). The values actually stem from hashCode() being defined in our superclass Object.

The former exercise Inserting strings into a Set. involved instances of class String having well defined equals() and hashCode() implementations. To resolve this issue we thus have to override not just equals() but hashCode() as well:

public class Coordinate {

  private int x, y;
 ...

  @Override
  public int hashCode() {
    // See last answer (06/16/2014) in
    // http://stackoverflow.com/questions/16629893/good-hashcode-implementation
    return Long.valueOf(x * 31 + y).hashCode();
  }
}

This yields:

c12.hashCode() and c12Duplicate.hashCode():33,33

And finally:

The set contains 2 elements:
(1|2)
(4|1)