Taming Tiger: The Collections Framework

Get a handle on the new collection types and additional features of existing classes and interfaces

You're probably quite familiar with the new Java™ 5 language support for generics, the concurrent utility libraries, and their effect on the Collections Framework, but those aren't the only changes to the Collections Framework libraries in Tiger. This month, John Zukowski covers several other enhancements, including the new collection types and the additional features of existing classes and interfaces.

Share:

John Zukowski (jaz@zukowski.net), President, JZ Ventures, Inc.

Author photoJohn Zukowski conducts strategic Java consulting with JZ Ventures, Inc. and is working with SavaJe Technologies to develop a next-generation mobile phone platform. His latest books are The Definitive Guide to Java Swing, Third Edition (Apress, June 2005) and Mastering Java 2, J2SE 1.4 (Sybex, April 2002).



19 July 2005

Also available in Japanese

The lion's share of interest in JDK 5.0 has been on the more prominent features of the Collections Framework, like the new language-level changes to support generics and the concurrent collections utility libraries found in the java.util.concurrent package. In fact, prior developerWorks content covered just that in "Concurrent collections" and the "Introduction to generic types in JDK 5.0" tutorial. But other enhancements haven't been given nearly enough attention. In this article, I'll look at three other changes: the updated Arrays and Collections classes and the new Queue interface, with its PriorityQueue implementation.

Arrays

The Arrays class provides a series of static utility methods for working with arrays, those indexed data structures that are of a fixed size. Prior to 5.0, the class had binarySearch(), equals(), fill(), and sort() methods for each of the different array types you could have for primitive data types and a generic Object type. An additional asList() method for converting an Object array to a List is still available. Tiger adds to the set with hashCode() and toString() for all and deepEquals(), deepHashCode(), and deepToString() methods specific to Object arrays. In total, 21 new methods are available:

  • public static boolean deepEquals(Object[] a1, Object[] a2)
  • public static int deepHashCode(Object[] a)
  • public static String deepToString(Object[] a)
  • public static int hashCode(boolean[] a)
  • public static int hashCode(byte[] a)
  • public static int hashCode(char[] a)
  • public static int hashCode(double[] a)
  • public static int hashCode(float[] a)
  • public static int hashCode(int[] a)
  • public static int hashCode(long[] a)
  • public static int hashCode(Object[] a)
  • public static int hashCode(short[] a)
  • public static String toString(boolean[] a)
  • public static String toString(byte[] a)
  • public static String toString(char[] a)
  • public static String toString(double[] a)
  • public static String toString(float[] a)
  • public static String toString(int[] a)
  • public static String toString(long[] a)
  • public static String toString(Object[] a)
  • public static String toString(short[] a)

This change to the utility class is the first since the Collections Framework debuted in J2SE 1.2. I'm not sure why Sun waited so long to introduce the changes, but they are welcome additions to the series of available helper methods.

The first of the new methods added is hashCode(). For any array type, you can call Arrays.hashCode(arrayVar) and get a well formed hash code. This hash code could be used as a key into a HashMap or for any other related purpose. Unless you know how to generate a good hash code, the one generated from the Arrays class is bound to be better, resulting in fewer collisions. It happens to generate a code that is equivalent to having a List of the same elements.

When creating your own classes, you are supposed to provide both equals() and hashCode() methods together. With the help of the new hashCode() method of Arrays, you can use it to generate a hash code for any local array types, instead of rolling your own each time you need it.

The other method that is now available for all array types is toString(). For any array type, you can call Arrays.toString(arrayVar) to get a comma-separated list of elements, surrounded by square brackets, as shown by the program in Listing 1:

Listing 1. Stringifying with Arrays.toString
import java.util.Arrays;

public class ArgsToString {
  public static void main(String args[]) {
    System.out.println(Arrays.toString(args));
  }
}

Listing 2 shows the results:

Listing 2. Results from Listing 1
>java ArgsToString One Two Three
  [One, Two, Three]

The new deepEquals(), deepHashCode(), and deepToString() methods work like their non-deep counterparts, but instead of stopping and working with each element of the top-level array, they continue to dig deeper into the multi-dimensional array to generate results.

While not a new method, the asList() method does actually behave differently with 5.0. Previously, the method accepted an Object[] array as its argument. Now, because of the variable-argument list features of Tiger, any comma-separated list will work, as shown in Listing 3:

Listing 3. Arrays.asList differences
import java.util.Arrays;

public class AsList {
  public static void main(String args[]) {
    // Before
    List before = Arrays.asList(args);
    // After
    List after = Arrays.asList("One", "Two", "Three");
  }
}

The two examples in Listing 3 won't necessarily produce the same results if the elements passed to the command-line are different, but it does show how the language-level changes of Tiger extend the functionality of the original asList() method of Arrays.


Collections

The complementary class of Arrays for the different collections is the Collections class. Again, the class isn't new, but the features of the class have been extended for 5.0. You now have 13 new methods:

  • checkedCollection()
  • checkedSet()
  • checkedSortedSet()
  • checkedList()
  • checkedMap()
  • checkedSortedMap()
  • emptySet()
  • emptyList()
  • emptyMap()
  • reverseOrder()
  • frequency()
  • disjoint()
  • addAll()

The six checked*() methods work similar to the six synchronized*() and unmodifiable*() methods. With the synchronized*() methods, you provide a collection to the method and are given back a synchronized, thread-safe version of that same collection. With unmodifiable*(), you get back a read-only view of the specified collection. The checked*() operation requires a second and possibly third argument, in addition to the collection (as shown in Listing 4), and returns a dynamically typesafe view of the collection:

Listing 4. Checked collections
  public static <E> Collection<E> checkedCollection(
    Collection<E> c, Class<E> type)
  public static <E> Set<E> checkedSet(
    Set<E> s, Class<E> type)
  public static <E> SortedSet<E> checkedSortedSet(
    SortedSet<E> s, Class<E> type)
  public static <E> List<E> checkedList(
    List<E> list, Class<E> type)
  public static <K,V> Map<K,V> checkedMap(
    Map<K,V> m, Class<K> keyType, Class<V> valueType)
  public static <K,V> SortedMap<K,V> checkedSortedMap(
    SortedMap<K,V> m, Class<K> keyType, Class<V> valueType)

With the Java 5.0 platform, you would think that because you declared a collection as generic (Collection<String> c = new HashSet<String>();), you wouldn't need run-time type checks, but if you were to pass your String version of HashSet to a utility method that only works with a non-generic Set, then that method could incorrectly add a non-String element to the set. Temporarily modifying the program to add the run-time check with Collection<String> c = Collections.checkedCollection(new HashSet<String>(), String.class); enables you to find the source of the problem quickly.

The three empty*() methods -- emptySet(), emptyList(), and emptyMap()-- generate empty immutable collections. While you can certainly create empty collections with method calls like new ArraySet(), that collection would then need to go through one of the unmodifiable*() methods to make sure the new collection was immutable. The empty methods provide empty, read-only collections in a more optimal way.


Queue interface

One of the bigger changes to the 5.0 Collections Framework is the addition of the new base interface Queue. While described in the "Concurrent Collections" tip (see Resources), the use of the interface is not limited to concurrency. In computer science, a queue data structure is your basic first-in, first-out (FIFO) structure. Items are added to the end and removed from the beginning. Not only do you add and remove elements, you can also look at the queue to see what is there. Listing 5 shows the five methods of the Queue interface:

Listing 5. The Queue interface
  public boolean offer(Object element)
  public Object remove()
  public Object poll()
  public Object element()
  public Object peek()

Keep in mind that Queue extends from the Collection interface, so implementations of the Queue interface also implement Collection. When using an implementation of Queue, you should try to limit yourself to methods of the interface. For instance, adding elements to a Queue could be done with the add() method of Collection, throwing an unchecked exception on failure. Instead, if a sized queue was full, the offer() method returns false, not causing you to deal with exceptions when full.

Several implementations of the Queue interface exist in the java.util.concurrent package, but not all of them. The LinkedList class has been retrofitted with the Queue interface with JDK 5.0 and the PriorityQueue has been added with JDK 5.0. The remaining implementations -- ArrayBlockingQueue, ConcurrentLinkedQueue, DelayQueue, LinkedBlockingQueue, PriorityBlockingQueue, and SynchronousQueue -- are all part of the java.util.concurrent package.

Because LinkedList isn't new, let's look at the new PriorityQueue class. As shown in Listing 6, you can create one in six ways. When a Comparator isn't available, the natural ordering of the elements is used to determine priority. If the elements don't implement the Comparable interface, then that is a run-time error:

Listing 6. PriorityQueue constructors
  PriorityQueue() 
  PriorityQueue(Collection<? extends E> c) 
  PriorityQueue(int initialCapacity) 
  PriorityQueue(int initialCapacity, Comparator<? super E> comparator) 
  PriorityQueue(PriorityQueue<? extends E> c) 
  PriorityQueue(SortedSet<? extends E> c)

To demonstrate the use of PriorityQueue, the program in Listing 7 adds all the command line elements and processes them in alphabetical order. Had the queue structure been a LinkedList, the order would have been the more typical FIFO order, but a PriorityQueue relies on priorities to order elements:

Listing 7. PriorityQueue usage
import java.util.*;
import java.util.concurrent.*;

public class Priority {
  public static void main(String args[]) {
    Queue<String> queue =
      new PriorityQueue<String>(Arrays.asList(args));
    String element;
    while ((element = queue.poll()) != null) {
	    System.out.println(element);
    }
  }
}

Listing 8 shows the output of running the program with a command line of one two three four:

Listing 8. Results from Listing 7
>java Priority one two three four
  four
  one
  three
  two

One thing to mention about the new Queue interface, with relation to the Collections class: Methods checkedQueue(), emptyQueue(), synchronizedQueue(), and unmodifiableQueue() are all missing from the Collections class. According to bug reports, all cases but checkedQueue() were done purposely. For synchronizedQueue(), the concurrent collections are better alternatives then a mere wrapper. The others were deemed unnecessary. Perhaps checkedQueue() (and checkedBlockingQueue()) will be added with the Tiger/6.0 release.


Download

DescriptionNameSize
Sample codej-tiger07195-source.zip1 KB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=89019
ArticleTitle=Taming Tiger: The Collections Framework
publish-date=07192005