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 4
Page 4 of 5

Collections doesn't support adaptation of arrays of primitive types, only arrays of Objects. It does this by providing a factory method (Arrays.asList(Object[])), which returns a List, thus further eliminating the need for a new class (such as JGL's ObjectArray).

Collections abstract classes

Collections encourages the creation of brand new implementations of its core collection interfaces by providing abstract "part-implemented" classes as halfway houses between the pure abstract of interfaces and the pure concrete of instantiatable classes.

Abstract collectionPurpose
AbstractCollectionThis class provides a skeletal implementation of the Collection interface, to minimize the effort required to implement this interface.
AbstractListThis class provides a skeletal implementation of the List interface to minimize the effort required to implement this interface backed by a "random access" data store (such as an array).
AbstractMapThis class provides a skeletal implementation of the Map interface, to minimize the effort required to implement this interface.
AbstractSequentialListThis class provides a skeletal implementation of the List interface to minimize the effort required to implement this interface backed by a "sequential access" data store (such as a linked list).
AbstractSetThis class provides a skeletal implementation of the Set interface to minimize the effort required to implement this interface.

Table 11. The Collections Framework's abstract collection classes

These abstract classes are yet another clue as to the philosophical differences between the two frameworks: Collections is primarily a framework of Java interfaces, with its collection implementations provided as one example implementation only. The provision in Collections (but not in JGL) of part-implemented abstract collections clearly suggests that application developers themselves are encouraged to implement more Collection classes. JGL's design shows no signs of similar thinking. Its documentation repeatedly states that JGL's implementations are highly optimized, implying that implementing your own improved classes (whether for container or algorithm) would be counterproductive (and most definitely going against the core generic programming philosophy of not reinventing wheels).

It's either in the core, or it isn't

There's one advantage Collections has over JGL which may dwarf any advantage, whether technologically superior or not, JGL has over Collections: Collections is a product of Sun, and has -- quite logically -- been given Core API status. While this means that anyone who uses a Java 2-compliant Java environment should have access to the entire Collections Framework, it also means -- and this is much more important in the long run -- that other standard (Core/Extension) APIs eventually will adopt the Collection "look" for APIs, wherever this makes sense.

Here are just a few examples of places in the existing 1.1 Core API where Collections should logically impose its interfaces in post-Java 2 releases:

  • AWT/Swing's Container class is a collection of GUI Components. It should become a Collection.

  • JDBC's ResultSet is collection of database records. It should become a Collection.

  • java.lang.ThreadGroup is a collection of threads. It should become a Collection.

Personally, I have my doubts that Sun will tinker with all these thoroughly established, bread-and-butter Java APIs in the ways suggested above. Developers dislike constantly changing APIs, instead preferring stability, regardless of the level of internal API inconsistency. How many Java developers do you know who didn't curse the AWT quakes between JDK 1.0.2 and JDK 1.1? I suspect Sun has learned an important lesson from its mid-course AWT corrections and will use the Collections interfaces for future APIs only, and leave existing ones as they are -- warts and all.

The frameworks in action: A few examples

Up to now, this article has hovered mere nanometers above both frameworks' APIs. In real life, though, people use tools based on how useful they are in helping them achieve real-life goals. I will now present some example (real-life) problems, along with a sample of solutions using JGL and Collections. I shall leave it entirely up to you, the reader, to pick which respective solutions you find more readable, elegant, efficient, modifiable, maintainable, and so on.

Example 1: Genetic mutations

The program requirements for this example are as follows: Given two strands of parental DNA, sexually combine them to form a child strand. Apply a user-defined amount of gene mutation to the combining process.

Here's a solution using the JGL framework:

//======================================================================== // GeneMutations (C) December 1998 JavaWorld - All Rights Reserved // ------------- // // History: // -------- // 28/09/98: started this file from new.java //========================================================================

import COM.objectspace.jgl.*; // for Array import COM.objectspace.jgl.algorithms.*; // for Sorting import COM.objectspace.jgl.functions.*; // for Print

import java.util.Random; // JDK 1.2 Random class !

public class GeneMutations {

// Some constants.. protected final static int STRAND_LENGTH = 20;

protected final static String[] DNA_BASES = {"A","C","G","T"};

/************************************************************************* * main() entry point *************************************************************************/ public static void main (String[] args) {

// read user-specified mutation rate (0%..100%) int mutationRate = Integer.parseInt(args[0]);

// create parent strands Sequence parentGeneSequenceA = randomGeneSequence(STRAND_LENGTH); Sequence parentGeneSequenceB = randomGeneSequence(STRAND_LENGTH);

System.out.println("Sequence A: " + parentGeneSequenceA); System.out.println("Sequence B: " + parentGeneSequenceB); System.out.println("");

// create child strand from parent strands Sequence childSequence = combineGeneSequences( parentGeneSequenceA, parentGeneSequenceB, mutationRate);

System.out.println("Child Seq : " + childSequence); }

/************************************************************************* * Generate a random gene sequence of a given length *************************************************************************/ static Sequence randomGeneSequence(int geneSequenceLength) {

DList sequence = new DList(); for (int i=0; i < geneSequenceLength; i++) { sequence.add( randomGene() ); }

return sequence; }

/************************************************************************* * Combine two "parent" gene sequences to produce a "child" sequence * As in nature, the combining can be affected by mutations (the virulence * of which is controlled by the mutationRate). *************************************************************************/ static Sequence combineGeneSequences(Sequence a, Sequence b, int mutationRate) {

DList newSequence = new DList( a.size() );

InputIterator aStart = a.start(); InputIterator aFinish = a.finish(); InputIterator bStart = b.start(); OutputIterator newStart = newSequence.start();

BinaryFunction combineGenes = new CombineGenes(mutationRate);

Transforming.transform(aStart, aFinish, bStart, newStart, combineGenes);

return newSequence; }

static String randomGene() { int baseNumber = (int)(Math.random()*4); return DNA_BASES[baseNumber]; }

} // End of Class GeneMutations

class CombineGenes implements BinaryFunction {

protected int mutationRate; protected Random randomizer;

/************************************************************************* * CombineGenes function constructor. * * @param mutationRate is a percentage value 0..100. * 0 Means no mutations * 100 Means mutations on every base (i.e. total destruction of genetic material) *************************************************************************/ CombineGenes(int mutationRate) { this.mutationRate = mutationRate;

randomizer = new Random(); }

public Object execute(Object baseA, Object baseB) {

boolean mutateBase = (randomizer.nextInt(100) < mutationRate);

if ( mutateBase ) { return GeneMutations.randomGene(); } else {

// evenly distribute genes from both parents (50-50) boolean fromOrganismA = randomizer.nextBoolean();

return fromOrganismA ? baseA : baseB; } }

} // End of JGL function CombineGenes

And here's a solution using the Collections Framework:

//======================================================================== // GeneMutations2 (C) December 1998 JavaWorld - All Rights Reserved // -------------- // // History: // -------- // 28/09/98: started this file from new.java // 05/10/98: Modified to use Java Collections instead of JGL //========================================================================

import java.util.*;

public class GeneMutations2 {

final static int STRAND_LENGTH = 20; final static String[] BASES = {"A","C","G","T"}; final static Random rnd = new Random();

public static void main (String[] args) { int mutationRate = Integer.parseInt(args[0]);

List parentGeneSequenceA = randomGeneSequence(STRAND_LENGTH); List parentGeneSequenceB = randomGeneSequence(STRAND_LENGTH);

System.out.println("Sequence A: " + parentGeneSequenceA); System.out.println("Sequence B: " + parentGeneSequenceB); System.out.println();

List childSequence = combineGeneSequences( parentGeneSequenceA, parentGeneSequenceB, mutationRate);

System.out.println("Child Seq : " + childSequence); }

/** * Generate a random gene sequence of a given length */ static List randomGeneSequence(int geneSequenceLength) { List sequence = new ArrayList(); for (int i=0; i < geneSequenceLength; i++) sequence.add(randomGene()); return sequence; }

static String randomGene() { int baseNumber = rnd.nextInt(4); return BASES[baseNumber]; }

/** * Combine two "parent" gene sequences to produce a "child" sequence. * As in nature, the combining can be affected by mutations (the virulence * of which is controlled by the mutationRate). * mutationRate is a percentage value 0..100. 0 Means no mutations. */ static List combineGeneSequences(List a, List b, int mutationRate) { List newSequence = new ArrayList(a.size()); for (Iterator ai = a.iterator(), bi = b.iterator(); ai.hasNext(); ) { newSequence.add(combine(ai.next(), bi.next(), mutationRate)); } return newSequence; }

static Object combine(Object baseA, Object baseB, int mutationRate){ return (rnd.nextInt(100) < mutationRate ? randomGene() : (rnd.nextBoolean() ? baseA : baseB)); } }

Example 2: Object-oriented database query

The program requirements for this example are as follows: Given an (in-core) object database containing Employee and Consultant objects, let an end-user query the database to find consultants who are paid more than a user-specified hourly rate.

Before reviewing the framework-dependent solution samples, take a look at the Person, PaidPerson, Employee, and Consultant classes that form the container framework-independent context for our problem.

class Person {

String title; // Mr., Miss, .. String firstName; String lastName; Date dateOfBirth;

Person(String lastName) { this.lastName = lastName; }

public String toString() { return lastName; } }

class PaidPerson extends Person {

double hourlyRate;

PaidPerson(String lastName, double hourlyRate) { super(lastName); this.hourlyRate = hourlyRate; }

public String toString() { return super.toString() + " " + hourlyRate + " $/hr."; }

}

class Employee extends PaidPerson {

String employeeID;

Employee(String lastName, double yearlySalary) { super(lastName, yearlySalary/365*8); } }

class Consultant extends PaidPerson {

Consultant(String lastName, double hourlyRate) { super(lastName, hourlyRate); }

}

Here's a solution to the database query problem using the JGL framework:

1 2 3 4 5 Page 4
Page 4 of 5