A Set of Coordinate instances

exercise No. 206

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 The Importance of hashCode() and equals() in Collections. 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)