Summary
This entry is a replacement for "When Generics Fail" (which was deleted). In that, I began with casting solution, thinking that there wasn't a generic solution, then Krzysztof Sobolewski corrected me. So instead, I will compare the two solutions.

Advertisement

Both solutions work, but with generics you get static type checking -- although you may decide that the syntax is fairly heavy in the generic version.

Here's the old solution, for comparison. It uses Object and casting, instead of generics.

A test framework

To prevent code duplication and to provide consistency among tests, I've put the basic functionality of the test process into a framework. This is another example of the Template Method design pattern. In this case, however, the core code (that doesn't change) is in the timedTest() method in a separate class Tester, and the Test.test() method is overridden for each particular test:

To use the framework, you create an array of Test objects and an array of container objects to be tested, and pass these to the Tester constructor. When you call run(), each test is run for each one of the container objects in your list, for three different data set sizes (given by the sizes array, which you can also change):

The first constructor just uses the name of the class as the header, but if you want an alternate name, use the second constructor.

If you need to perform special initialization, override the initialize() method. This takes the container object and returns it after it's been initialized. You can see in test() that the result is assigned back to the container reference, and this allows you to replace the container with a completely different initialized container.

Tester also manages the formatting of the output, so it contains a fieldWidth that you can change. This is used to produce format strings.

Choosing between Lists

All Lists hold their elements in the sequence in which you insert those elements. This program is a performance test for the most essential of the List operations:

The performance of HashSet is generally superior to TreeSet, but in particular for addition and lookup, the two most important operations. The only reason TreeSet exists is because it maintains its elements in sorted order, so you use it only when you need a sorted Set. Because of the internal structure necessary to support sorting and because iteration is something you're more likely to do, iteration is faster with a TreeSet than a HashSet.

Note that LinkedHashSet is somewhat more expensive for insertions than HashSet; this is because of the extra cost of maintaining the linked list along with the hashed container. However, traversal appears to be more expensive for LinkedHashSet.

Choosing between Maps

This program gives an indication of the trade-off between Map implementations:

The first constructor just uses the name of the class as the header, but if you want an alternate name, use the second constructor.

If you need to perform special initialization, override the initialize() method. This takes the container object and returns it after its been initialized. You can see in test() that the result is assigned back to the container reference, and this allows you to replace the container with a completely different initialized container.

Tester also manages the formatting of the output, so it contains a fieldWidth that you can change. This is used to produce format strings.

To compare array access to container access (primarily against ArrayList), a special test is created for arrays by wrapping an array as a List using Arrays.asList( ). Only three of the tests can be performed in this case, because you cannot insert or remove elements from an array. Notice that the initialize() method throws away the container argument (so that argument is a dummy object) and creates a completely new object using Generated.array() (which was defined in the Arrays chapter).

In main(), the definition of lists requires an explicit type argument specification to give a hint to the Arrays.asList() method call.