Creating Collectible Classes

Contact Us or call 1-877-932-8228
Creating Collectible Classes

Creating Collectible Classes

hashCode and equals

Both the equals(Object) method and hashCode() methods are used by methods in the Collections API. Note also that:

  • The Map classes use them to determine if a key is already present.
  • The Set classes use them to determine if an object is already in the set.
  • All collections classes use them in contains and related methods.

Oracle specifies that the behavior of hashCode should be "consistent with equals", meaning that two objects that compare as equal should return the same hash code. The reverse is not required; two objects with the same hash code might be unequal, since hash codes provide "bins" for storing objects.

This is critical when writing collectible classes. For example, the implementation of HashSet, which should reject duplicate entries, compares a candidate entry's hashcode against that of each object currently in the collection. It will only call equals if it finds a matching hash code. If no hash code matches, it assumes that the candidate object must not match any already present in the set.

Comparable and Comparator

Sorted collections can sort elements in two ways:

  1. By the natural order of the elements - objects that implement the Comparable interface have a natural order.
  2. By using a third-party class that implements Comparator.

Comparable specifies one method: compareTo(Object o) returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.

Comparator specifies two methods:

  1. compare(Object a, Object b) returns a negative integer, zero, or a positive integer if a is less than, equal to, or greater than b.
  2. equals(Object o), as in Object, is used not to compare the stored values for value, but to determine if two comparator classes can be considered equal. However, note that the behavior of the compare method should be consistent with equals, as Oracle's documentation advises; that is, compare(a, b) should return 0 when a.equals(b) returns true

As an example: TreeSet uses a tree structure to store items. Tthe tree part of the name is just for identifying the algorithm used for storage; you cannot make use of any of the node-related behaviors from the outside. There are several forms of constructors, most notably:

  • TreeSet() uses the natural order of the items, so they must implement the Comparable interface (and the items should be mutually comparable, to avoid casting exceptions being thrown during a comparison).
  • TreeSet(Comparator c) - uses the specified Comparator to compare the items for ordering (and, again, the mutually comparable caveat applies).

Code Sample:

Java-Collections/Demos/UseComparable.java
import java.util.*;

public class UseComparable {
  public static void main(String[] args)
                     throws java.io.IOException {
    String[] names = { "Sue", "Bill", "Tom", "Dave", "Andy",
                       "Mary", "Beth", "Bill", "Mike" };
    TreeSet sl = new TreeSet(Arrays.asList(names));
    Iterator it = sl.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }
  }
}

Since String implements Comparable, the names will appear in alphabetical order when we iterate through the set.

The Arrays class provides useful methods for working with arrays, some of which will return a collection backed by the array (the Collections classes contain useful methods for working with collections, some of which perform the reverse operation).

Creating a Comparable Class

Classes that implement the Comparable interface may be stored in ordered collections. They must have a int compareTo(Object other) method to implement the interface. Note the following:

  • It is said that these objects have a natural order as determined by this method.
  • The function returns a negative value for this object considered less than the object passed as parameter other, a positive value for this object considered greater than the object passed as parameter other, and 0 if they are equal.
  • Since this capability is built into the object, it is important that it the results of the compareTo method match with the results of the equals and hashCode methods.

Creating and Using a Comparator Class

Classes that implement the Comparator interface may be also used with ordered collections, but the collection must be constructed with an explicit reference to an instance of the Comparator. The Comparator is a separate class that will compare two instances of your class to determine the ordering.

The interface specifies int compare(Object a, Object b). The function returns a negative value for an object considered less than the object b, a positive value for b considered greater than a, and 0 if they are equal.

It is still important that the results of the compareTo method match with the results of the objects' equals method. Note that you should usually implement this method to avoid "ties" for objects that would not be considered equal. For example, for two different employees who coincidentally have the same name would be returned in an indeterminate order.

Code Sample:

Java-Collections/Demos/UseComparator.java
import java.util.*;

public class UseComparator {
  
  public static void main(String[] args) throws java.io.IOException {
    String[] names = { "Sue", "Bill", "Tom", "Dave", "Andy",
                       "Mary", "Beth", "Bill", "Mike" };

    TreeSet s2 = new TreeSet(new ReverseComparator());
    s2.addAll(Arrays.asList(names));
    
    Iterator it = s2.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }
  }
}

class ReverseComparator implements Comparator {
  public int compare(Object o1, Object o2) {
    if (o1 instanceof String && o2 instanceof String)
      return -((String)o1).compareTo((String)o2);
    else throw new ClassCastException("Objects are not Strings");
  }
}

The compare method of our comparator makes use of the existing compareTo method in String and simply inverts the result. Note that the objects being compared are tested to make sure both are String objects. If the test fails, the method throws a ClassCastException.

Code Sample:

Java-Collections/Demos/UseComparableAndComparator.java
import java.util.*;

public class UseComparableAndComparator {
  
  public static void main(String[] args) throws java.io.IOException {
    String[] names = { "Sue", "Bill", "Tom", "Dave", "Andy",
                       "Mary", "Beth", "Bill", "Mike" };

    TreeSet sl = new TreeSet(Arrays.asList(names));
    Iterator it = sl.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }

    TreeSet s2 = new TreeSet(new ReverseComparator());
    s2.addAll(Arrays.asList(names));
    
    it = s2.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }
  }
}

Since all the collections store are references, it will not use a lot of memory to store the same references in different collections. This creates an analog to a set of table indexes in a database

Next