Java Collections

April 13th, 2009

We just finished covering data types in class, including a pretty in depth look at the Java Collections libraries. The book covered the basics, but there are a few concepts I wanted to touch on that may be helpful in the upcoming project.

Java can automatically sort collections for you, provided you tell it how to. There are two ways to do this:

Comparable
Unlike equals and toString, which are provide by the Object superclass, the compareTo method is optional. Classes may choose to provide a description of how to compare instances of that class by implementing the Comparable interface. That interface contains a single method compareTo(T o) that provides a numeric result indicating if the object on which it is called is less than (result is less than zero), greater than (result is greater than zero), or equal to (result is zero) the object passed into the parameter.

For instance, for a rather generic Person class, the following implementation would provide sorting based on the person’s last name:

1
2
3
4
5
6
7
class Person implements Comparable<Person> {
  // Attributes
 
  public int compareTo(Person other) {
    return this.lastName.compareTo(other.lastName);
  }
}

Note the use of generics in the implements clause. This lets us write the compareTo method to explicitly take a Person instance rather than the generic Object, saving us from having to deal with the casting. Also note that in many cases, the compareTo method (like equals and hashCode) will often chain down to a simpler data type’s Comparable implementation, in this case String.

One benefit of this approach is that the class is self-describing. In addition to describing how to compare two instances for equality, the class also describes how to order instances of the class. The drawback to this inclusion is that it’s a single method of sorting. There isn’t a really clean way in this approach to distinguish if the person objects should be sorted by last name or, say, social security number. That’s where the second approach comes in.

Comparator
Similar to Comparable, the Comparator interface contains a single method that returns a numeric result. However, a comparator is meant to be a separate class from the one being compared (whereas the Comparable interface is meant to be used by the class being compared). As such, the compareTo method takes two arguments, meant to be the two objects being compared.

For instance, we could write the same ordering functionality as above in an external Comparator implementation:

1
2
3
4
5
class PersonComparator implements Comparator<Person> {
  public int compareTo(Person p1, Person p2) {
    return p1.getLastName().compareTo(p2.getLastName());
  }
}

Again, the use of generics allows us to write the compareTo signature using specific classes rather than just Object references. Note that this implementation is outside the Person class and thus doesn’t have the same access to private variables that the Comparable implementation uses. This shouldn’t be a problem, but it’s worth noting.

There are a number of conventions in the collections APIs to make life easier. For instance, almost all collection classes contain a constructor that takes a Collection as a parameter. That constructor will populate the new collection with the entities in the parameter. For instance:

1
List newList = new ArrayList(oldList);

Keep in mind this doesn’t do a deep copy of each element in the old list. It simply adds all of the object references in the old list to the new one. In other words, the first item in each of these two lists will refer to the same object. However, they are different lists and one can be manipulated without affecting the order or contents of the other.

This is often a good approach when returning collections from a method call. It can prevent the caller from mangling a collection internal to another class, as well as prevent the caller from holding an open iteration of the collection, preventing the original owner of the collection from making any changes to its contents.

This is also a way to jump between different collection types. For instance, if you have a list that may contain duplicate items that you want removed, you can easily throw the items of the list into a set, which will automatically remove the duplicates in the process:

1
2
List hasDups; // Initialized elsewhere
Set noDups = new HashSet(hasDups);

The same functionality can often be achieved through an addAll(Collection c) method that many collections provide. This method will do exactly that: add all elements from one collection to another:

1
2
3
List oldList; // Initialized elsewhere
List newList = new ArrayList();
newList.addAll(oldList);

There are other handy features, such as the ability to make a collection thread-safe or constants for empty collections, that I won’t go into just yet. This has already been more of a wall of text than I like to make these sorts of entries. :)

Comments are closed.