Creating Custom Generic Collections with J2SE 5.0

Creating Custom Generic Collections with J2SE 5.0

y last article discussed how to use J2SE 5.0’s new collection features, allowing you to designate a specific type to be used with a collection. Additionally, the new “for each” construct lets you access collections without the need for an “iterator”. However, that was only half the story. This article shows you how to create collections that are compatible with the latest features of J2SE.

Creating Classes that Support Generics
First you must lean how to create a class that allows a “generic type”. This means that whenever your class is instantiated, you can also specify one or more Java types to be associated with that class. To illustrate this, consider this simple example class presented in Listing 1.

Notice how the class in Listing 1 is declared. It specifies three generics between the beginning angle brackets. Those generics are placeholders for real types. When you declare a class of this type, you can specify a class to take the place of ONE, TWO, and THREE. If you do not, then the class will use the default type of Object.

Here’s how to instantiate a class of type Example.

   Example example = new     Example();

The preceding code substitutes the specific Double, Integer, and String types for the “ONE,” “TWO,” and “THREE” placeholders in Listing 1. You can see that these variables have these types by the following three lines that set their values.

     example.setOne(1.5);     example.setTwo(2);     example.setThree("Three");

Now that you understand how to create a custom class that makes use of generics, it’s far simpler to create a custom collection class that makes use of generics.

Creating a Queue Class
A queue can be a very useful data structure. To understand the functions of a queue picture the line of people waiting for a ride at an amusement park. People enter the line at the back. They wait their turn, and eventually reach the front of the line. The order does not change.

This same principle can be applied to a queue class. There are two methods, named “push” and “pop”. You use the push method to place objects onto the queue, and the pop method to remove items from the queue. For example, if you use the push method to add three objects onto the queue, then calling pop three times will remove those three objects in the same order. This is the same as the amusement park line. If three people enter the line in a specific order, they will get access to the ride, in the same order.

The following code shows how to implement a Java queue that makes use of generics.

   package com.heatonresearch.examples.collections;      import java.util.*;      public class Queue {        private ArrayList list = new ArrayList();        public void push(T obj) {       list.add(obj);     }        public T pop() throws QueueException {       if (size() == 0)         throw new QueueException(             "Tried to pop something from the queue, " +             "when it was empty");       T result = list.get(0);       list.remove(0);       return result;     }        public boolean isEmpty() {       return list.isEmpty();     }        public int size() {       return list.size();     }        public void clear() {       list.clear();     }   }

The preceding code declares the queue class so that it accepts one generic type.

public class Queue

The generic type “T” is the class type that will be placed onto the queue. To store the items on a queue, the class creates an ArrayList that accepts types of “T” as well.

The push method is very simple. It accepts a single object, of the generic type “T”, and adds it to the ArrayList.

The pop method is slightly more complex. First, if you try to pop an object from the queue, and there are no objects on the queue, the class throws an exception of type QueueException. Here’s the QueueException class.

   package com.heatonresearch.examples.collections;   public class QueueException extends Exception {     public QueueException(String msg) {       super(msg);     }   }

Here’s the code that throws the QueueException:

   if (size() == 0)     throw new QueueException(       "Tried to pop something from the queue, " +        "when it was empty");

If the queue is not empty, the method retrieves the last element from the queue, stores it in a variable named result, then removes that item from the list. The following lines of code accomplish this.

     T result = list.get(0);     list.remove(0);     return result;

Note that the temporary variable is also of the generic type “T”. To ensure the greatest level of compatibility when this class is used with real Java types that will “stand in” for the generic type, it’s important to always use the generic type whenever you must access these variables.

Testing the Queue Class
The following class serves to test the “generic” queue.

   package com.heatonresearch.examples.collections;      public class TestQueue {        public static void main(String args[]) {       Queue queue = new Queue();       queue.push(1);       queue.push(2);       queue.push(3);       try {         System.out.println("Pop 1:" + queue.pop());         System.out.println("Pop 2:" + queue.pop());         System.out.println("Pop 3:" + queue.pop());       } catch (QueueException e) {         e.printStackTrace();       }     }   }

The queue created in the preceding code accepts Integer objects only”

   Queue queue = new Queue();

The test next adds three integers to the queue.

   queue.push(1);   queue.push(2);   queue.push(3);

Notice that the numbers added to the queue are primitive types. Because of J2SE’s autoboxing feature, these primitive int types are automatically converted into Integer objects.

Next, the test retrieves the objects using the pop method. The test catches the QueueException, in case the queue is empty. The results of popping three numbers from this queue will be.

   1   2   3

Although shown here as a queue that accepts Integers, because of generics, this queue class will work with any Java object.

Creating a Peekable Stack Collection
Here’s a more complex collection type that implements a stack that allows you to look ahead, or “peek” before actually removing an object. You can look ahead using either an iterator or J2SE 5.0’s new “for each” construct.

The PeekableStack class is a first-in-last-out (FILO) stack that lets you iterate over the current stack contents. The implementation uses two classes. First, the PeekableStack class implements the actual stack. Second, the PeekableStackIterator class implements a “Java Standard” Iterator class that you can use to iterate over the stack. Listing 2 shows the PeekableStack class.

Notice that the PeekableStack class in Listing 2 implements the Iterable interface. This is necessary to support the new J2SE 5.0 “for-each” construct. The Iterable interface specifies that your collection supports the “iterator” method, which returns an iterator. Without this interface, your class is not compatible with the new “for-each” construct.

The peekable stack contains both push and pop methods, just like the queue. The push method is only slightly more complex than the queue. The push method adds the object onto the stack and increments the version.

The version variable allows the PeekableStackIterator class to ensure that no modifications have taken place. When the iterator is created the iterator keeps a copy of the current version number. If any changes to the stack occur through calls to the push method, the version numbers will not match; that mismatch causes the iterator to throw a ConcurrentModificationException.

The pop method is a little more complex. First it must determine the last element in the list, which it does by obtaining the size of the list and subtracting one.

   int last = list.size() - 1;

If this results in a number less than zero, then the stack was empty, so the pop method returns null.

   if (last < 0)     return null;

If there is a "last element" in the stack, then retrieve it from the list. After retrieving the item successfully from the list you can remove it.

   T result = list.get(last);   list.remove(last);

Finally, return the object that was retrieved from the list.

   return result;

To support "for each" iteration, the PeekableStack class's iterator method returns a "Java Standard" Iterator class that you can use to iterate over all the objects contained in the stack. The iterator method creates a new iterator and returns it.

   PeekableStackIterator peekableStackIterator =      new PeekableStackIterator(this, list);

As you can see, the iterator class accepts the current stack and the stack's list of items as constructor parameters. These values will be used by the PeekableStackIterator, which is covered in the next section.

Creating a Peekable Stack Iterator
If the PeekableStack class is to be used with Java's new "for each" construct, then you must create a "Java Standard" iterator. The implementation for a PeekableStackIterator class is shown in Listing 3.

In Listing 3, the iterator does not actually change the value of the stack in any way; instead, the iterator keeps track of its current position in the element list and always returns the next element. Because this information is stored in the iteration class itself, it would be possible to have several iterators operating on the same stack.

The following program tests the Peekable stack.

   package com.heatonresearch.examples.collections;      import java.util.*;      public class TestPeekableStack {         public static void main(String args[]) {         PeekableStack stack = new          PeekableStack();         stack.push(1);         stack.push(2);         stack.push(3);            for (int i : stack) {            System.out.println(i);         }            System.out.println("Pop 1:" + stack.pop());         System.out.println("Pop 2:" + stack.pop());         System.out.println("Pop 3:" + stack.pop());      }   }

As you can see, three items are added to the stack. Then these three items are displayed using the new "for-each" construct.

   for( int i: stack)   {     System.out.println( i );   }

So, you've seen how to successfully implement a collection that supports the new J2SE conventions for both generics and the "for each" construct. As you can see, it's relatively easy to create collections that are compatible with the new constructs in J2SE 5.0 by taking advantage of generics and implementing the appropriate interfaces. You'll find that such collection classes will integrate seamlessly into J2SE 5.0.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist