The battle of the container frameworks: which should you use?

We compare ObjectSpace's JGL and Sun's Collections Framework to help you pick the best option

1 2 3 4 5 Page 3
Page 3 of 5
JGL ContainerJGL Interfaces ImplementedClosest equivalent Collections containerCollections interfaces implemented
ArrayContainer, SequenceArrayList, VectorCollection, List
DequeContainer, Sequence  
DListContainer, SequenceLinkedListCollection, List
SListContainer, Sequence(LinkedList)Collection, List
HashMapContainerHashMap, HashtableMap
OrderedMapContainerTreeMapMap, SortedMap
HashSetContainer, SetHashSetCollection, Set
OrderedSetContainer, SetTreeSetCollection, Set, SortedSet
PriorityQueueContainer  
QueueContainer  
StackContainerStackCollection, List

Table 6. JGL and Collections container implementations

Both frameworks' container implementations enhance their respective basic container interfaces with extra "convenience" methods. A useful discussion of these methods (and their cross-framework comparison) would unfortunately require far more time and effort than the scope of this article permits, therefore, exploring these methods is left to the enterprising reader. One important thing to note, though, is that unlike JGL, Collections doesn't add many of these convenience methods to its collection implementations because such methods necessarily lie outside the interface framework: a philosophical no-no for Collections! Even when Collections does add noninterface methods, the majority of these methods appear to be mere performance-tuning knobs, not truly "functional" methods.

Algorithms

The Collections Framework focuses almost exclusively on containers, the abstract data structures designed to hold objects. Yet computer science rarely looks at data structures without, in the same breath, also looking at associated algorithms. JGL acknowledges this fact by treating containers and algorithms on an equal footing. Collections doesn't, yet it does scatter a few algorithms in a number of places. Table 7 compares the two frameworks for ready-to-use algorithms.

JGL Class

JGL Algorithm

[Number of overloaded variants]

Closest Collections Equivalent
ApplyingApplying.forEach() [2] 
 Applying.inject() [2] 
ComparingComparing.equal() [2] 
 Comparing.lexicographicalCompare() [4] 
 Comparing.median() [1] 
 Comparing.mismatch() [4] 
CopyingCopying.copy() [3]Collections.copy() [1]
 Copying.copyBackward() [1] 
CountingCounting.count() [2] 
 Counting.countIf() [2] 
 Counting.accumulate() [4] 
 Counting.adjacentDifference() [6] 
FillingFill.fill() [2]Collections.fill() [1]
 Fill.fillN() [1]Collections.nCopies() [1]
FilteringFiltering.reject() [2] 
 Filtering.select() [2] 
 Filtering.unique() [4] 
 Filtering.uniqueCopy() [6] 
FindingFinding.find() [2] 
 Finding.findIf() [2] 
 Finding.adjacentFind() [4] 
 Finding.detect() [2] 
 Finding.every() [2] 
 Finding.some() [2] 
HashingHashing.orderedHash() [2] 
 Hashing.unorderedHash() [2] 
HeapHeap.makeHeap() [2] 
 Heap.popHeap() [2] 
 Heap.pushHeap() [2] 
 Heap.sortHeap() [2] 
MinMaxMinMax.maxElement() [4]Collections.max() [2]
 MinMax.minElement() [4]Collections.min() [2]
OrderedSetOperationsOrderedSetOperations.setDifference() [3]Collection.removeAll() [1]
 OrderedSetOperations.setIntersection() [3]Collection.retainAll() [1]
 OrderedSetOperations.setSymmetricDifference() [3] 
 OrderedSetOperations.setUnion() [3]Collection.addAll() [1]
 OrderedSetOperations.includes() [3]Collection.containsAll() [1]
PermutingPermuting.nextPermutation() [2] 
 Permuting.prevPermutation() [2] 
PrintingPrinting.print() [2] 
 Printing.prinln() [2] 
 Printing.toString() [2] 
RemovingRemoving.remove() [2]Collection.remove() [1]
 Removing.removeIf() [2] 
 Removing.removeCopy() [2] 
 Removing.removeCopyIf() [2] 
ReplacingReplacing.replace() [2] 
 Replacing.replaceIf() [2] 
 Replacing.replaceCopy() [3] 
 Replacing.replaceCopyIf() [3] 
ReversingReversing.reverse() [2]Collections.reverse() [1]
 Reversing.reverseCopy() [3] 
RotatingRotating.rotate() [1] 
 Rotating.rotateCopy() [1] 
SetOperationsSetOperations.setDifference() [3] 
 SetOperations.setIntersection() [3] 
 SetOperations.setSymmetricDifference() [3] 
 SetOperations.setUnion() [3] 
 SetOperations.includes() [3] 
ShufflingShuffling.randomShuffle() [4]Collections.shuffle() [1]
SortingSorting.sort() [4]Collections.sort() [2]
 Sorting.iterSort() [4] 
SwappingSwapping.swapRanges() [2] 
 Swapping.iterSwap() [1] 
TransformingTransforming.transform() [6] 
 Transforming.collect() [2] 

Table 7. Ready-to-use algorithm support in JGL and Collections

(In the Collections column above, note that Collection.x() stands for a method x() in interface Collection, whereas Collections.x() [note additional s] stands for a static method in class Collections.)

JGL's support for a wide range of algorithms is plain to see in Table 7. Collections provides a smaller, less-flexible set of ready-to-use algorithms -- mainly copying, filling, sorting, binary search, set operations, min/max, simple object removal, and collection shuffling. Surprisingly, JGL doesn't provide a binary search.

While browsing the source code for Collections Framework algorithms, I stumbled upon an artifact that clearly highlights (and confirms) the philosophical differences between JGL and Collections: The class java.util.Arrays contains seven, near-identical implementations of a sort algorithm: one implementation for each of the primitive types byte, char, short, int, long, float and double. While this code duplication is tucked away inside the Collections Framework, and hence doesn't affect the application programmer, it shows a programming approach that is diametrically opposite to JGL's generic programming style.

Other major framework differences

So far we've concentrated on obvious framework aspects that bear comparison: general framework architecture (underlying philosophy, packages, interface hierarchies and APIs), container implementations, and algorithm implementations. This effort has already highlighted many differences between ObjectSpace's JGL and Sun's Collections Framework. The remainder of the article will explore more differences resulting from aspects in each framework that lack obvious analogs in the framework's respective counterpart.

What? No function objects?

JGL's treatment of algorithms as library pillars naturally leads to a feature that is completely lacking in Collections: function objects (and predicate objects, which are simply function objects that embody a boolean function). Function objects are objects that you pass to an algorithm to modify and/or extend that algorithm's default behavior. In other words, function objects are the procedural-language equivalent of method pointers. My 18 months of experience with JGL lets me firmly conclude that function and predicate objects are the most powerful and flexible aspect of JGL, since they let you create and extend algorithms without having to descend into low-level, implementation-specific techniques such as (algorithm) subclassing or reuse-sabotaging algorithm inlining. To use function objects, you simply construct application-specific functionality as a function class, and tell one of JGL's standard, generic algorithms to use it. After a very short while, this programming style starts to feel like playing with Lego™ blocks, which is a Good Thing. (Both JGL examples shown later in this article use function and predicate objects. My November 1998 JavaWorld article, "Speeding up batch file processing," also illustrates this technique in a real-life project.)

How to add function objects to Collections

Of course, function objects are based on a few trivial JGL interfaces, similar to the one below:

interface Function {
   Object execute(Object object);
}

So there is no reason -- at first sight -- why you can't infuse your Collections-based code with similar flexibility.

But there's an important difference between adding function objects yourself and having them supported by the framework. The lack of direct support for function objects in Collections presents two unfortunate disadvantages:

  • Without explicit and manifest support for function objects, many developers (except JavaWorld readers!) will remain eternally oblivious to the significant benefits of the function-objects approach (read: plug-ins for standard algorithms).

  • Collections algorithms can't be customized beyond the mechanism afforded by the use of Comparator objects. Comparator objects aren't flexible enough to act as function objects.

The algorithms Collections does offer weren't designed to be customized using function object plug-ins. Furthermore, it's impossible for you, the application programmer, to retrofit the Collections algorithms to support such plug-ins. If you really want to use function objects and the Collections Framework together, you'll have to rewrite the algorithms from scratch -- exactly the thing that JGL's generic programming style strives to eliminate.

Synchronized versus unsynchronized access

From a multithreaded software viewpoint, JGL and Collections differ greatly. JGL containers use synchronized methods for their accessors and manipulators whereas Collections containers do not. This major difference exists in the name of performance (Collections), and in the name of programmer-friendliness (JGL). Leaving out the synchronized keywords on relevant Collections methods eliminates any object locking/unlocking overhead inherent as a result of synchronized code execution.

Luckily, Sun realizes as much as ObjectSpace that thread-safety is critical to any Java software, so the Collections Framework allows you to wrap a thread-safe wrapper around a thread-unsafe collection. This wrapping is done using utility methods in the Collections class. Table 8 lists these methods.

Collections utility method

Purpose

static Collection synchronizedCollection(Collection c)Returns a synchronized (thread-safe) Collection backed by the specified Collection.
static List synchronizedList(List list)Returns a synchronized (thread-safe) List backed by the specified List.
static Map synchronizedMap(Map m)Returns a synchronized (thread-safe) Map backed by the specified Map.
static Set synchronizedSet(Set s)Returns a synchronized (thread-safe) Set backed by the specified Set.
static SortedMap synchronizedSortedMap(SortedMap m)Returns a synchronized (thread-safe) SortedMap backed by the specified SortedMap.
static SortedSet synchronizedSortedSet(SortedSet s)Returns a synchronized (thread-safe) SortedSet backed by the specified SortedSet.

Table 8. Collections factory utility methods to transform thread-unsafe collections into thread-safe collections

Collections read-only wrappers

In a similar vein to providing synchronized-access wrapper objects, Collections also lets you obtain read-only wrappers for any of its containers. Table 9 lists the relevant factory methods of class Collections.

Collections utility method

Purpose

static List unmodifiableList(List list)Returns an unmodifiable view of the specified List.
static Map unmodifiableMap(Map m)Returns an unmodifiable view of the specified Map.
static Set unmodifiableSet(Set s)Returns an unmodifiable view of the specified Set.
static SortedMap unmodifiableSortedMap(SortedMap m)Returns an unmodifiable view of the specified SortedMap.
static SortedSet unmodifiableSortedSet(SortedSet s)Returns an unmodifiable view of the specified SortedSet.

Table 9. Collections factory utility methods to construct read-only views of specific collections

JGL doesn't include the concept of, or explicit support for, read-only containers.

Minor framework differences

By now we've seen the major framework differences between JGL and Collections -- differences that will cause headaches for those who must port from one framework to the other, and will even lead some to determine that migrating from one framework to another is simply too costly or problematic.

The next set of differences are, in my opinion, far less important than those discussed so far, but still warrant consideration.

Support for pre-Java 2 Vector and Hashtable

Before the advent of the Java 2 platform, Java had two main workhorse container classes: java.util's Vector and Hashtable. Both frameworks acknowledge these "legacy" bread-and-butter classes in different ways.

JGL has its adapter class VectorAdapter, which lets you use any Vector-based code and enhance it using JGL features. By wrapping a VectorAdapter around any Vector, your Vector objects essentially become JGL-compatible containers, allowing you to throw any JGL algorithm at them.

Collections, on the other hand, does something ObjectSpace's developers probably would have given their left arms to do, but never could: Collections directly enhances Vector to implement interface Collection! So in the Java 2 platform Collections universe, Vector doesn't require adaptation, it simply is a Collection (and a List).

(As an aside, note how flexible Java's class hierarchies are: with Java 2, Vector implements brand new interfaces, adds new methods, and yet is still 100 percent backward-compatible with JDK 1.1's Vector. Bjarne Stroustrup, try that in C++!)

Hashtable receives the same kind of "let's rewrite the rule book" treatment from Collections: it has now become a Map (but remember, Maps are not Collections).

Support for native arrays

Collections and JGL again differ in the way they provide extra support for native Java arrays (which of course never can descend from some type that implements an interface, and therefore never can be directly first-class containers in any Java container framework).

JGL features adapter classes that can turn native arrays (for example, main()'s one-dimensional "args" String array) into JGL-compatible containers. These adapter classes are listed in Table 10.

Fixed-length native array-to-container adapters

Resizing native array-to-container adapters

ArrayAdaptern/a
BooleanArrayBooleanBuffer
ByteArrayByteBuffer
CharArrayCharBuffer
DoubleArrayDoubleBuffer
FloatArrayFloatBuffer
IntArrayIntBuffer
LongArrayLongBuffer
ObjectArrayn/a
ShortArrayShortBuffer
VectorArrayn/a

Table 10. JGL native array adapter classes

Note that, with JGL, native arrays containing primitive values (boolean, char, byte, short, int, long, float, double) are intelligently adapted: values are wrapped or unwrapped into and/or from objects as required.

1 2 3 4 5 Page 3
Page 3 of 5