Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

Vectors, Hashtables, and Enumerations

Java Programming, Lecture Notes # 76, Revised 01/11/98.

Preface

Students in Prof. Baldwin's Intermediate Java Programming classes at ACC are responsible for knowing and understanding all of the material in this lesson.

Introduction

Several previous lessons dealt with data structures of the "reinvent your own" variety. The Java API provides some data structures which you can often use without the requirement to invent your own. The classes that provide these structures are contained in the java.util package. In this lesson, we will be interested in the Vector class, the Hashtable class, and the Enumeration interface.

The java.util package also provides a Stack class, but according to several books on the subject, it doesn't behave the way we normally expect a stack to behave (I haven't tested it myself). In particular, the Stack class apparently extends the Vector class which makes it possible to instantiate an object of the Stack class, and then access methods of the Vector class that violate the LIFO behavior that we normally expect from a stack.

If I have the time later, I will prepare a lesson on creating a stack using the Vector class that doesn't have this shortcoming.

Enumeration Interface

Let's begin with what is possibly the most abstract and the most difficult of the three to get a grip on. Enumeration is not a class, it is an interface.
 
Therefore, any class can be designed to implement the Enumeration interface.
If a class implements the Enumeration interface, and if the methods of that class are properly designed and coded, then an object of that class can be used to enumerate an object of the class for which it was specifically designed.

What do we mean when we say that we are going to enumerate an object? We mean that we will provide a pair of methods by which a using program can:

Note the use of the words specifically designed above. There is no such thing as a general-purpose enumeration object or a general-purpose enumeration class. All enumeration objects are instances of a class specifically designed to enumerate objects of some specific class.

A class that implements the Enumeration interface must provide a definition for the following two methods that are declared in the interface.
 
  public boolean hasMoreElements()
  public Object nextElement()
The benefits of the Enumeration interface occur when a class that deals with sets of objects has the ability to provide an object of a class that implements the Enumeration interface (an enumeration object) capable of enumerating an instance of the class that deals with sets of objects.

For example, if an object of a linked-list class has the ability to provide an enumeration object on itself, code within the scope of the linked list object can obtain that enumeration object and use it to interate through all the nodes in the linked list.

The methods of the Enumeration interface can be invoked on the enumeration object to provide that code with a sequence of references to the nodes in the list. Then depending on access control considerations, the code can access the individual nodes in that specific linked-list object to do something useful.

It is important to note that an enumeration object is not really an object of the Enumeration class, because there is no such class. Rather, Enumeration is an interface and it is not possible to instantiate objects of an interface type.

However, if an object is instantiated from a class that implements the Enumeration interface, then insofar as the two methods declared in the Enumeration interface are concerned, the object can be treated as if it were of type Enumeration.

Unless the class from which the enumeration object is instantiated contains members that are not declared in the Enumeration interface, you really don't need to care about the actual class from which it was instantiated. You can always refer to it as type Enumeration.

It is also important to note that an enumeration object normally won't contain any hard data. Rather, it will normally contain a reference to the specific object that it was instantiated to enumerate, and such other data as may be necessary for it to reliably execute its two methods. Note however, that an enumeration object designed to enumerate an object which is a complex data structure may require very complex methods to accomplish its task..

When the nextElement() method of an enumeration object returns a reference to an object, that reference will always be of the generic type Object and it will normally be necessary for you to downcast it to its true type in order to do much with it that is useful.

As mentioned earlier, and repeated here for emphasis, an enumeration object can exist only as a partner to another object containing data to be enumerated. In other words, there is no such thing as a useful standalone enumeration object . An enumeration object exists for the sole purpose of providing its methods to enumerate another object of a specific class.

Sample Enumeration Program

Enumeration objects don't need to be complex. The following sample program illustrates a simple class that stores String data in an array and a simple enumeration object that can be used to enumerate an object of that class.

The program defines a class named MyDataStruct that creates a simple data structure consisting of an array of String data.

The class provides a method that returns an object of a class that implements the Enumeration interface (an enumeration object ).

The two methods of the Enumeration interface class can then be invoked on the enumeration object to iterate through the original data structure retrieving each data element in the structure. The enumeration object serves as a structured pathway into the object that it was instantiated to enumerate.

One of the two methods of the Enumeration interface can be used to determine if there are any elements in the data structure that haven't already been retrieved.

The other method in the Enumeration interface can be used to retrieve the next element.

In this program, the controlling class

The program was tested using JDK 1.1.3 under Win 95. The output from running the program is shown in the comments at the beginning of the complete program listing in a later section.

Interesting Code Fragments in Enumeration Program

The first interesting code fragment is the beginnings of the class used to instantiate an enumeration object . As you can see, this class implements the Enumeration interface. As you can also see, it contains the three essential instance variables. In this case, the object being enumerated is a simple array of references to objects of type Object.
 
class MyEnumerator implements Enumeration{
  int count;//store iteration counter here
  int length;//store array length here
  Object[] dataArray;//store reference to data array here
The next interesting code fragment is the constructor for the class. Enumeration objects are not instantiated directly by the program that will use the enumeration object . Rather, they are instantiated and returned by the object that is to be enumerated, normally as a result of a method call on that object. Only that object can provide the required information when the enumeration object is instantiated.

This constructor receives the information for the three critical instance variables and stores that information in those variables.
 
  MyEnumerator(int count,int length,Object[] dataArray){
    this.count = count;
    this.length = length;
    this.dataArray = dataArray;
  }//end constructor
The next interesting code fragment is one of the two required methods in a class that implements the Enumeration interface. This method returns a boolean indicating whether or not there are any more elements that haven't already been retrieved. In the case of a simple array of Object references, this is a very simple method. In a more complex structure, such as a tree for example, this method might be more complex.
 
  public boolean hasMoreElements(){
    return (count < length);
  }//end hasMoreElements
The next interesting code fragment is the other of the two methods required of a class that implements the Enumeration interface. This method will return a reference to the next element from the data structure. The return value will be typed as the generic type Object. Again, for a simple array of Object references, this is a very simple method. For a more complicated structure such as a tree, this could be a very complex method.
 
  public Object nextElement(){
    return dataArray[count++];
  }//end nextElement
And that's all there is to the class that is used to instantiate the Enumerator object in this simple program.

The next interesting code fragment is the beginning of the class definition for a class that can maintain an array of Object references and provide an enumerator object to methods that request it. For purposes of illustration, this class was made very simple with the initialization of the data in the array being hard-coded into the program.
 
class MyDataStruct{
  String[] data;//array of refs to String objects
  
  MyDataStruct(){//constructor
    data = new String[4];
    data[0] = "zero";
    data[1] = "one";
    data[2] = "two";
    data[3] = "three";
  }//end constructor 
The next interesting code fragment is a method that must exist, in some form, in every class that supports enumeration. This is the method that, when called, instantiates and returns an enumeration object specific to the object on which the method was invoked. In this case, the method invokes the parameterized constructor of the MyEnumerator class and returns the object that results from that invocation.

You might want to go back and compare the parameters passed to the constructor with the formal argument list of the constructor defined earlier.

The first parameter is used to initialize a counter in the enumerator object (this could be hard-coded into the constructor and as such would not require a parameter).

The second parameter is the length of the array.

The third parameter is a reference to the array in the object that is to be enumerated (this could also be formulated differently using the this reference).
 
  Enumeration getEnum(){
    return new MyEnumerator(0,data.length,data);
  }//end getEnum()
And that is the end of the simple data structure class being used to illustrate Enumeration in this sample program.

The remaining code is the code in the controlling class that

 
class Enum01{//controlling class
  public static void main(String[] args){
    MyDataStruct myDataStruct = new MyDataStruct();
    Enumeration myEnumeration = myDataStruct.getEnum();
    while(myEnumeration.hasMoreElements()){
      System.out.println(myEnumeration.nextElement());
    }//end while loop
  }//end main()
}//end controlling class
So, as you can see, although Enumeration can be very complex for certain kinds of data structures, complexity is not a requirement. Hopefully this will give you a firm grounding in the concept of Enumeration so that we can make use of Enumeration in the discussions of the Vector and Hashtable classes that follows.

Program Listing for Enumeration Program

A complete listing of the program follows.
 
/*File Enum01.java Copyright 1997, R.G.Baldwin
This program illustrates the Enumeration interface.

The program defines a class named MyDataStruct that creates
a simple data structure consisting of an array of String
data.

The class provides a method that returns an object of a 
class that implements the Enumeration interface (an 
enumeration object ).

The two methods of the Enumeration interface class can 
then be invoked on the enumeration object  to iterate 
through the original data structure fetching each data 
element in the structure.

The two methods of the Enumeration interface are:
  public boolean hasMoreElements()
  public Object nextElement()

The first method can be used to determine if there are any
elements in the data structure that haven't already been
fetched.  The second method can be used to fetch the next
element.

The controlling class instantiates an object of type
MyDataStruct, invokes the getEnum() method to get an
enumeration object , and then uses that object to interate
through the data structure fetching and displaying each
of the elements in the structure.

The program was tested using JDK 1.1.3 under Win 95.

Running this program produces the following output:
  
zero
one
two
three
**********************************************************/
import java.util.*;
//=======================================================//

//The following class is used by the MyDataStruct class
// to instantiate an object that implements the Enumeration
// interface.
class MyEnumerator implements Enumeration{
  int count;//store iteration counter here
  int length;//store array length here
  Object[] dataArray;//store reference to data array here
  //-----------------------------------------------------//
  
  //Constructor
  MyEnumerator(int count,int length,Object[] dataArray){
    this.count = count;
    this.length = length;
    this.dataArray = dataArray;
  }//end constructor
  //-----------------------------------------------------//
  
  //This method defines one of the methods that are
  // declared in the Enumeration interface.
  public boolean hasMoreElements(){
    return (count < length);
  }//end hasMoreElements
  //-----------------------------------------------------//
  
  //This method defines the other method that is declared
  // in the Enumeration interface.
  public Object nextElement(){
    return dataArray[count++];
  }//end nextElement
  
}//end class MyEnumerator
//=======================================================//

//This class can be used to instantiate a simple data
// structure object that has the ability to provide an
// enumeration object  to a using program.
class MyDataStruct{
  String[] data;
  //-----------------------------------------------------//
  
  MyDataStruct(){//constructor
    //Hard code the data for illustration purposes only
    data = new String[4];
    data[0] = "zero";
    data[1] = "one";
    data[2] = "two";
    data[3] = "three";
  }//end constructor 
  //-----------------------------------------------------//
  
  //This method will return an enumeration object  to a
  // using program.
  Enumeration getEnum(){
    return new MyEnumerator(0,data.length,data);
  }//end getEnum()
  //-----------------------------------------------------//
}//end class MyDataStruct
//=======================================================//

class Enum01{//controlling class
  public static void main(String[] args){
    //Instantiate an object of type MyDataStruct
    MyDataStruct myDataStruct = new MyDataStruct();
    
    //Get an enumeration object  that describes the object
    // of type MyDataStruct
    Enumeration myEnumeration = myDataStruct.getEnum();
    
    //Use the enumeration object  to iterate and display
    // each element in the object of type MyDataStruct.
    while(myEnumeration.hasMoreElements()){
      System.out.println(myEnumeration.nextElement());
    }//end while loop
  }//end main()
}//end controlling class
//=======================================================//
.

Vector Class

Our next topic in this lesson is the Vector class. An object of the Vector class is essentially an array that can grow or shrink as needed to accommodate adding and removing items after the Vector object has been created.

This is a very handy class in that it provides the convenience of indexed access and at the same time does not confine you to the use of an array whose size is established at compile time.

Two terms are important when discussing objects of the Vector class are capacity and capacityIncrement.

The capacity specifies how many objects can be stored in the Vector object at its current size, and is always at least as large as the number of objects stored in the Vector object (elementCount). Whenever the capacity needs to be increased, it increases in chunks the size of capacityIncrement.

There are three overloaded constructors that allow you to control how the object is constructed in terms of initial capacity and capacityIncrement. You should take a look at the options in the JDK documentation.

There are a large number of methods that allow you to use a Vector object. A sampling of some of those methods follows. You should review the remaining methods in the JDK documentation package.
 
  • addElement(Object) - Adds the specified object to the end of this vector, increasing its size by one. 
  • removeElement(Object) - Removes the first occurrence of the argument from this vector. 
  • removeElementAt(int) - Deletes the object at the specified index. 
  • capacity() - Returns the current capacity of this vector. 
  • contains(Object) - Tests if the specified object is a component in this vector. 
  • elementAt(int) - Returns the object at the specified index. 
  • elements() - Returns an enumeration of the objects of this vector. 
  • firstElement() - Returns the first object of this vector. 
  • insertElementAt(Object, int) - Inserts the specified object as a component in this vector at the specified index
  • isEmpty() - Tests if this vector has no components. 
  • lastElement() - Returns the last component of the vector. 
Note in particular the italicized method named elements(). This is the method that returns an enumeration object for an object of the Vector class. We will be using the elements() method in a sample program later in this lesson.

Hashtable Class

Objects of the Hashtable class are a little more complicated than objects of the Vector class. Fortunately for us, the folks at JavaSoft have already done most of the hard work, and all we have to do is to take advantage of what they provided for us to use.

A hashtable stores object references by mapping keys to values. Any non-null object can be used as a key or as a value.

Objects used as keys in a hashtable must implement the hashCode() method. This is a method that converts an object into a (hopefully) unique identifier by way of applying a mathematical algorithm to the object. hashCode() is a method in the Object class and a default version is inherited by all classes in Java.

Many of the classes in the standard Java API override the hashCode() method for objects of that class. This includes the String class that we will be using for key values in a subsequent sample program.

Similarly, objects used as keys in a hashtable must implement the equals() method. The equals() method is defined in the Object class and overridden by many classes (including String) in the standard Java API.

Two terms of interest when using hashtables are capacity and load factor, because these terms affect the efficiency of the object relative to storing and retrieving objects. You can find a more detailed discussion of these terms in the JDK documentation.

So, the bottom line is that when you want to store an object in a hashtable, you provide two objects. One is used as a key and the other is stored as a value. To retrieve the object that has been stored, you simply provide the key again, and the code in the method retrieves and returns the object that was stored as a value.

All value objects are typed as the generic type Object.

There are three overloaded Hashtable constructors that allow you to instantiate Hashtable objects for different values of capacity and load factor. You should take a look at your options in the JDK documentation in this regard.

As with Vector, there are quite a few of methods available to help you use hashtables. A sampling of these methods follows. You should review the remaining methods in the JDK documentation.
 
contains(Object) - Tests if some key maps into the specified value in this hashtable. 
containsKey(Object) - Tests if the specified object is a key in this hashtable. 
elements() - Returns an enumeration of the values in this hashtable. 
keys() - Returns an enumeration of the keys in this hashtable. 
get(Object) - Returns the value to which the specified key is mapped in this hashtable. 
isEmpty() - Tests if this hashtable maps no keys to values. 
put(Object, Object) - Maps the specified key to the specified value in this hashtable. 
It is important to note that references to first-class objects can be stored in a hashtable; not just data. In the sample program that follows, we will be storing references to objects that contain instance methods, and then retrieving the references to the objects for the purpose of invoking the instance methods contained in the objects.

Sample Program

This program is not intended to accomplish anything useful other than to illustrate how to use the Vector and Hashtable classes, and the Enumeration interface.

In addition, it illustrates some other important Java programming concepts such as registering a list of objects for some particular purpose, processing all the objects on the list of registered objects, working at the generic Object level and downcasting when needed, etc.

The program is fairly long and somewhat complex, so we will take it in small steps.

Interesting Code Fragments

The scenario is as follows. This program provides the capability to instantiate objects describing people where each object contains a name, an age, and a weight. The program also provides the capability to compare two such objects on the basis of either age or weight to determine which is the lesser of the two.

A controlling class is used to used to test the program. The main() method of the controlling class instantiates some objects containing the names, ages, and weights of several people.

The main method of the controlling class also instantiates an object which serves as a manager for comparing these objects in pairs to report which is younger and which is lighter.

A method named registerPair() of the CompareManager class is invoked to register three pairs of objects for later comparison.

These actions are shown in the following code fragment.
 
  public static void main(String[] args){
    //Instantiate a manager to handle the comparisons
    CompareManager compareManager = new CompareManager();
    
    //Register three pairs of data objects with the
    // manager
    compareManager.registerPair(new Data("Tom",65,180),
                                 new Data("Dick",60,170));
    compareManager.registerPair(new Data("Harry",40,160),
                                 new Data("Dick",60,170));
    compareManager.registerPair(new Data("Harry",40,160),
                                  new Data("Tom",65,180));
Note that these objects are submitted as anonymous objects, although that isn't a requirement of the program.

The manager creates a list of pairs of such objects as they are submitted (registered), and when requested to do so, compares and reports on all of the pairs contained in the registered list. (Actually, to keep things simple, the manager encapsulates each pair in an object and registers that object).

Therefore, this program illustrates the type of operation often referred to as registering objects, and then processing all of the registered objects upon request.

Registration lists occur in many different kinds of operations in Java such as event handling, Model-View- Controller using Observable, etc.

Note that all of the objects in this program are handled as generic Object types and downcast to the required type when needed.

Therefore, this program also illustrates the use of downcasting from Object to actual types in addition to the concepts and mechanics of registration.

When the controlling class asks the manager to perform the comparisons on all the objects in the list of registered objects, that request is made on the basis of a comparison of either AGE or WEIGHT. A class named CompareHow is defined for the sole purpose of defining symbolic constants for AGE and WEIGHT. This class also defines a symbolic constant for INVALID that is used to confirm that the program works as expected when a request is made to compare on an INVALID basis.

The next code fragment shows the main() method in the controlling class asking the CompareManager object to compare all of the registered pairs of objects and to report on the results. Note that three calls to compareAll() are made, one for each symbolic constants.
 
    compareManager.compareAll(CompareHow.AGE);
    compareManager.compareAll(CompareHow.WEIGHT);
    compareManager.compareAll(CompareHow.INVALID);
And that is all that happens in the main() method of the controlling class.

There is a small class definition for a class named Data. This class is used to encapsulate the name, age, and weight data for a person in an object. The code for this class is too simple to merit being shown in this section. You can see it in the complete program listing that follows near the end of the lesson.

Likewise, as mentioned earlier, there is a small class definition for a class named CompareHow whose sole purpose is to define the symbolic String constants for AGE, WEIGHT, and INVALID. These symbolic constants are used later as keys in a hashtable. Again, this class is too simple to merit being shown in this section.

An interface named Comparable is defined which declares a method named comparePair(). This method is implemented in several different classes in the program.
 
interface Comparable{
  public void comparePair(String how, Object obj1,
                                             Object obj2);
}//end Comparable interface
Therefore, another concept that is illustrated by this program is the definition and use of user-defined interfaces.

Note that the definition of the Comparable interface would also have been a good place to define the symbolic constants mentioned above since the definition of constants is allowed in an interface.

The manager class (which is instantiated by and communicated with by the controlling class) is named CompareManager. This class is used to

This class makes use of an object of a class named CompareTool to actually perform the comparisons when time comes to compare the objects. The object of the class named CompareTool does some fairly complicated things using a Hashtable object.

The list of registered pairs of objects is maintained in an object of type Vector. Therefore, this program illustrates the use of the Vector class for maintaining a list of registered objects.

The following code fragment shows the beginning of the definition of the CompareManager class where the instance variables for the references to the CompareTool object and the Vector object are declared. Note that the reference to the CompareTool object is maintained as a generic Object type.
 
class CompareManager{
  Object compareTool;
  Vector myListOfObjects;
Rather than to deal with pairs of individual objects in the registration list, this manager class defines an inner-class named PairOfObj that is used to encapsulate each pair of objects into a single object that is then registered by placing a reference to that object in the Vector object used to maintain the list.

An inner-class is used for this purpose as shown in the next code fragment. Although it is a very simple class, the fact that it is an inner class probably merits displaying and discussing it in this section.
 
  class PairOfObj{
    Object obj1;
    Object obj2;
    
    PairOfObj(Object obj1,Object obj2){//constructor
      this.obj1 = obj1;
      this.obj2 = obj2;
    }//end constructor
  }//end inner-class PairOfObj
The constructor for the CompareManager object (shown below) instantiates a CompareTool object named compareTool and a Vector object named myListOfObjects to use as described above.
 
  CompareManager(){//constructor for a manager object
    this.compareTool = new CompareTool();
    myListOfObjects = new Vector();
  }//end constructor
The CompareManager class contains a method named registerPair() that is invoked from the outside to register a pair of objects for later comparison. This method encapsulates references to the two incoming objects into a single object and then invokes the addElement() method on the Vector object to create the list of registered objects.

No provisions are made to remove objects from the list of registered objects as is frequently the case when a registration list is used to maintain a list of registered objects. Obviously, this wouldn't be difficult given the methods that are available to manipulate the contents of the Vector object. Also, this registration method doesn't make any effort to prevent duplicated objects in the list which is sometimes done.
 
  public void registerPair(Object obj1,Object obj2) {
    this.myListOfObjects.addElement(
                                 new PairOfObj(obj1,obj2));
  }//end registerPair
The CompareManager class also contains a method named compareAll() that is invoked to compare and report on all of the objects in the list of registered objects. This method has a single String parameter which is intended to be one of the symbolic constants defined earlier which specifies how the comparison is to be made (AGE or WEIGHT).

The compareAll() method invokes the elements() method on the Vector object which instantiates and returns an object that implements the Enumeration interface. In other words, it returns an enumeration object. On the basis of the previous discussion, you should be aware of what this enumeration object is, and what can be done with it.

Methods of the Enumeration interface are then used to extract each of the composite objects from the list of registered objects. Each of the objects in the pair is then extracted from the registered composite object, and passed, along with a string parameter specifying how to perform the comparison, to a method named comparePair() which is a method of the CompareTool object described earlier. Note that comparePair() is declared in the Comparable interface and must be defined in all classes that implement that interface.
 
  public void compareAll(String how){
    Enumeration myEnum = myListOfObjects.elements();
    
    while(myEnum.hasMoreElements()){
      Object aPairOfObj = myEnum.nextElement();
      ((Comparable)compareTool).comparePair(how,
                             ((PairOfObj)aPairOfObj).obj1,
                             ((PairOfObj)aPairOfObj).obj2);
    }//end while loop
  }//end compareAll
}//end CompareManager class
Note the requirements for downcasting from Object to the true type to be able to access the methods and variables of the objects which have been maintained as the generic type Object.

That is the end of the compareAll() method and the end of the CompareManager class as well.

Now we will discuss the CompareTool class from which an object is instantiated to actually perform the comparison between two objects.

This is a little tricky, so you may need to pay close attention.

This class contains two inner-classes named AgeCompare and WeightCompare. Each of these classes implements the Comparable interface, and therefore defines a method named comparePair().

However, the comparePair() methods differ between the two classes. In one case, the code is designed to compare two objects on the basis of the instance variable named age. In the other case, the method is designed to compare two objects on the basis of the instance variable named weight.

These are the two methods that are ultimately invoked to make the actual comparisons, with the choice between methods being based on a String object that is passed in with the objects that are to be compared. The String object specifies either AGE or WEIGHT.

The following code fragment shows the beginning of the CompareTool class and the declaration of a reference variable to a Hashtable object along with the instantiation of that object.

This code fragment also shows one of the inner-classes named AgeCompare discussed above. The two inner-classes are very similar, so only one is shown here. You can view the other one in the complete program listing near the end of this lesson.

Note that these inner classes define a method having the same name, comparePair(), as a method in their parent class. Also note that in all three cases this method implements the Comparable interface. However, the implementation in all three cases is different depending on the class in which is is defined and the needs of objects of that class. The code to actually make the comparisons is straightforward, once you get past the downcasting requirements.
 
class CompareTool implements Comparable{
  private Hashtable myHashTable = new Hashtable();
  //_____________________________________________________//

  private class AgeCompare implements Comparable {
    public void comparePair(String how,Object obj1,
                                             Object obj2){
      System.out.println("In AgeCompare method");
      Data temp1 = (Data)obj1;//Cast incoming objects to
      Data temp2 = (Data)obj2;// the correct type.
      //Make the comparison on age
      if(temp1.age<temp2.age)
        System.out.println(temp1.name + 
                         " is younger than " + temp2.name);
      else System.out.println(temp1.name + 
                     " is not younger than " + temp2.name);
    }//end trace()
  }//end inner-class AgeCompare
This is where things become a little more complicated, so pay close attention. The constructor for the CompareTool class instantiates an object of class Hashtable and stores (puts) two objects in the Hashtable along with appropriate key values.

One object that is stored in the Hashtable is a reference to an object of type AgeCompare along with a key value of AGE.

The other object that is stored in the Hashtable is a reference to an object of type WeightCompare along with a key value of WEIGHT.

Make certain that you understand that these two objects are instantiated from the inner classes described above and what that implies.
 
  public CompareTool(){//constructor
    myHashTable.put( CompareHow.AGE, new AgeCompare() );
    myHashTable.put( CompareHow.WEIGHT, 
                                     new WeightCompare() );
  }//end constructor 
Once the CompareTool object is constructed, it contains a very special Hashtable object. By invoking the get() method on the Hashtable object, passing one of the two keys as a parameter, it is possible to obtain a reference to an object that contains an instance method designed to perform a comparison between two objects on the basis of either age or weight. This method can then be invoked to actually perform the comparison.

Therefore, this program illustrates the use of a Hashtable object to store and retrieve references to objects containing useful instance methods (as opposed simply to data values) on the basis of a key value.

(This concept may seem very foreign to you at this point, but we will be making heavy use of the concept when we explore the use of the invoke() method of the Method class in the reflection API around lesson 262.)

The CompareTool class implements the Comparable interface, meaning that it defines the method named comparePair() also. This is the method that is called by the object of the CompareManager class to compare two objects. In addition to the references to the two objects that are to be compared, this method also receives a String parameter specifying how to make the comparison: AGE or WEIGHT.

However, this method doesn't actually make the comparison. Rather, it first tests to confirm that the incoming String constant is contained as a key in the Hashtable. If it is a valid key value, it invokes the get() method on the Hashtable object, passing that key as a parameter, and in return receives a reference to an object associated with that key. The referenced object contains a method of the same name, comparePair(). The comparePair() method of the referenced object is invoked to perform the actual comparison.

Therefore, by using the incoming key value to access the Hashtable object, this method gains access to another method that is designed to perform the comparison in a manner that is consistent with the key value.

This is an interesting concept on selection. In the case of this simple program, only two different methods are represented by objects in the Hashtable. However, there could be dozens, hundreds, or even thousands of different methods represented by objects in the hashtable, and selection among them could be made simply on the basis of the key value associated with each. In effect, this is an approach to selection that goes beyond if else and switch case.

Note that when this method named comparePair() invokes the method named comparePair(), it is invoking a method that is defined in one of the objects stored in the hashtable. It is not making a recursive call on itself.
 
  public void comparePair(String how, Object obj1,
                                              Object obj2){
    if(myHashTable.containsKey(how)){
      Object theMethod = myHashTable.get(how);
      ((Comparable)theMethod).comparePair(how,obj1,obj2 );
    }//end if
    else System.out.println(
                "Invalid Key, could throw exception here");
  }//end comparePair()
While this may not be the most straightforward way to accomplish this, it does illustrate the use of a Hashtable object in a rather interesting way. In particular, the Hashtable object doesn't simply contain data values, it contains references to objects that in turn contain methods of interest. Those methods are selectable on the basis of key values.

As mentioned earlier, we will be making heavy use of this concept when we use of the invoke() method of the Method class in the reflection API to create smart event adapters in the Advanced portion of the tutorial around lesson 262.

If the incoming String parameter is not contained as a key in the Hashtable, an "Invalid Key" message is displayed and no attempt is made to compare the objects. This would be a good place to throw an exception.

As mentioned earlier, this particular program isn't intended to be useful for anything other than to illustrate the use of the Vector, and Hashtable classes along with the Enumeration interface, and also to illustrate some other important concepts such as creating and maintaining a list of registered objects, downcasting from Object to true type, etc. It is also intended to get you prepared to understand what you will encounter in the Advanced portion of the tutorial in relation to the reflection API.

The output from the program is contained in the comments at the beginning of the program listing.

Program Listing

A complete program listings follows so you can see the interesting code fragments in context.
 
/*File Hash01.java Copyright 1997, R.G.Baldwin
This program is not intended to accomplish anything
particularly useful other than to illustrate how to use
the Vector and Hashtable, and the Enumeration 
interface.

This program was tested using JDK 1.1.3 under Win95.

The output from the program is:
  
In AgeCompare method
Tom is not younger than Dick
In AgeCompare method
Harry is younger than Dick
In AgeCompare method
Harry is younger than Tom
In WeightCompare method
Tom is not lighter than Dick
In WeightCompare method
Harry is lighter than Dick
In WeightCompare method
Harry is lighter than Tom
Invalid Key, could throw exception here
Invalid Key, could throw exception here
Invalid Key, could throw exception here
**********************************************************/
import java.util.*;
//=======================================================//

//This is the controlling class used to test everything
// else.
class Hash01{
  public static void main(String[] args){
    //Instantiate a manager to handle the comparisons
    CompareManager compareManager = new CompareManager();
    
    //Register three pairs of data objects with the
    // manager
    compareManager.registerPair(new Data("Tom",65,180),
                                 new Data("Dick",60,170));
    compareManager.registerPair(new Data("Harry",40,160),
                                 new Data("Dick",60,170));
    compareManager.registerPair(new Data("Harry",40,160),
                                  new Data("Tom",65,180));

    //Request comparison of all pairs of objects on the
    // basis of age and weight (separately). Also
    // request comparison on the basis of an invalid
    // parameter.
    compareManager.compareAll(CompareHow.AGE);
    compareManager.compareAll(CompareHow.WEIGHT);
    compareManager.compareAll(CompareHow.INVALID);
  }//end main
}//end class Hash01
//=======================================================//

//This is the class used to package the data for
// submission to the comparison manager.
class Data{
  String name;
  int age;
  int weight;
  
  Data(String name,int age,int weight){
    this.name = name;
    this.age = age;
    this.weight = weight;
  }//end constructor
}//end class Data
//=======================================================//

//Define some string constants that are used later as
// keys in a hash table.
class CompareHow{
  public static final String AGE = "AGE";
  public static final String WEIGHT = "WEIGHT";
  public static final String INVALID = "INVALID";
}//end class CompareHow
//=======================================================//

//Define the Comparable interface.  Note that it declares
// the method named comparePair() that is defined in
// several classes that implement the interface.
interface Comparable{
  public void comparePair(String how,Object obj1,
                                             Object obj2);
}//end Comparable interface
//=======================================================//

//This class is used to manage the process of comparing
// pairs of objects
class CompareManager{
  //Stores a reference to an object that does the work
  // of comparing objects on the basis of a String
  // parameter that define how the objects are to be 
  // compared.
  Object compareTool;
  
  //Stores a list of references to objects each of which
  // contains a pair of objects to be compared.
  Vector myListOfObjects;
  //_____________________________________________________//
  
  //Define an inner class of the CompareManager class
  // that is used to package a pair of incoming 
  // objects into a single object for storage in a
  // Vector object.
  class PairOfObj{
    Object obj1;
    Object obj2;
    
    PairOfObj(Object obj1,Object obj2){//constructor
      this.obj1 = obj1;
      this.obj2 = obj2;
    }//end constructor
  }//end inner-class PairOfObj
  //_____________________________________________________//
  
  CompareManager(){//constructor for a manager object
    //Instantiate a tool object which will be used to 
    // actually perform the comparisons
    this.compareTool = new CompareTool();
    //Instantiate a Vector object to contain a list of 
    // registered objects
    myListOfObjects = new Vector();
  }//end constructor
  //-----------------------------------------------------//

  //This method maintains a list of registered objects
  // in a Vector object where each registered object 
  // contains a pair of objects that are to be compared 
  // later all at the same time.
  public void registerPair(Object obj1,Object obj2) {
    this.myListOfObjects.addElement(
                                 new PairOfObj(obj1,obj2));
  }//end registerPair
  //-----------------------------------------------------//
 
  //This method compares all the pairs of objects contained
  // in the list of registered objects. The comparison is
  // performed on the basis of the how parameter (age or
  // weight).
  public void compareAll(String how){
    //Create an enumeration object for the objects in the 
    // list of registered objects.
    Enumeration myEnum = myListOfObjects.elements();
    
    //Use the enumeration object to process all the objects
    // in the list of registered objects.  Each registered
    // object contains a pair of objects that are to be
    // compared.
    while(myEnum.hasMoreElements()){
      //Get the next registered object
      Object aPairOfObj = myEnum.nextElement();
      //Extract and compare the pair of objects contained
      // in the registered object.
      ((Comparable)compareTool).comparePair(how,
                             ((PairOfObj)aPairOfObj).obj1,
                             ((PairOfObj)aPairOfObj).obj2);
    }//end while loop
  }//end compareAll
}//end CompareManager class
//=======================================================//

//This class is used to instantiate an object which
// performs the actual comparisons based on a parameter
// named how and using references to methods that are
// stored in a hash table along with key values that
// match the parameter named how.  The parameter named
// how is used to fetch a reference to a method from the
// hash table and that reference is used to invoke the
// method to which it refers.
class CompareTool implements Comparable{
  //Store references to different comparison methods in
  // a hash table for later reference.
  private Hashtable myHashTable = new Hashtable();
  //_____________________________________________________//
  
  //This inner class contains one of the methods used to 
  // compare objects. This one is designed to compare on 
  // the basis of the instance variable named age. Note
  // that the name of the method in this inner class is
  // the same as the name of a method in the parent class
  // of this class and is also the same as the method
  // declared in the interface named Comparable.
  private class AgeCompare implements Comparable {
    public void comparePair(String how,Object obj1,
                                             Object obj2){
      System.out.println("In AgeCompare method");
      Data temp1 = (Data)obj1;//Cast incoming objects to
      Data temp2 = (Data)obj2;// the correct type.
      //Make the comparison on age
      if(temp1.age<temp2.age)
        System.out.println(temp1.name + 
                         " is younger than " + temp2.name);
      else System.out.println(temp1.name + 
                     " is not younger than " + temp2.name);
    }//end trace()
  }//end inner-class AgeCompare
  //_____________________________________________________//

  //This inner class contains one of the methods used to 
  // compare objects. This one is designed to compare on 
  // the basis of the instance variable named weight. Note
  // that the name of the method in this inner class is
  // the same as the name of a method in the parent class
  // of this class and is also the same as the method
  // declared in the interface named Comparable.
  private class WeightCompare implements Comparable {
    public void comparePair(String how,Object obj1,
                                              Object obj2){
      System.out.println("In WeightCompare method");
      Data temp1 = (Data)obj1;//Cast incoming objects to
      Data temp2 = (Data)obj2;// the correct type.
      //Make the comparison on weight
      if(temp1.weight<temp2.weight)
        System.out.println(temp1.name + 
                         " is lighter than " + temp2.name);
      else System.out.println(temp1.name + 
                     " is not lighter than " + temp2.name);
    }//end trace()
  }//end inner-class WeightCompare
  //_____________________________________________________//

  //This is the constructor for the CompareTool class.
  // It instantiates two objects, each containing a 
  // method named comparePair() and stores references to
  // them in the hash table, each with a different key.
  // The key values are designed to match the how 
  // parameter that is passed in to specify how the 
  // comparison is to be performed.  Later, the how 
  // parameter is used to fetch the reference to a
  // particular method and that reference is used to
  // invoke the method.
  public CompareTool(){//constructor
    //Initialize myHashTable and store references to 
    // methods in a hash table using string constants from
    // the CompareHow class as keys.
    myHashTable.put( CompareHow.AGE, new AgeCompare() );
    myHashTable.put( CompareHow.WEIGHT, 
                                     new WeightCompare() );
  }//end constructor
  //-----------------------------------------------------//

  //This is the method that is called to compare two
  // objects on the basis of the how parameter.
  //Note that the name of this method is the same as the
  // name of a method in each of the inner classes, and 
  // is also the same as the name of the method declared
  // in the interface named Comparable.
  public void comparePair(String how, Object obj1,
                                              Object obj2){
    //Use incoming how parameter to extract the reference
    // to the correct method from the hash table to use for
    // this comparison and assign the reference to a local
    // variable named theMethod.  Test to confirm that the
    // value of the how parameter is actually a key in the
    // hashtable.  If not, simply display a message.  Note
    // that this would be a good place to throw an 
    // exception.
    if(myHashTable.containsKey(how)){
      Object theMethod = myHashTable.get(how);
    
      //Use the local variable named theMethod to invoke 
      // the correct comparison method on the incoming 
      // objects.
      //Note that this is the invocation of a method 
      // named comparePair() that is defined in one of 
      // the inner classes of this class and is contained
      // in the object referenced in the hash table.  It
      // is not a recursive call to this version of 
      // comparePair().
      ((Comparable)theMethod).comparePair(how,obj1,obj2 );
    }//end if
    else System.out.println(
                "Invalid Key, could throw exception here");
  }//end comparePair()
  //-----------------------------------------------------//
}//end CompareTool class
//=======================================================//

Review

Q - Write a Java program that meets the following specifications.
 
/* File SampProg153.java Copyright 1998, R.G.Baldwin
From lesson 76

Without viewing the solution that follows, write a Java
application that uses the Vector class to implement a
LIFO stack.  Make certain that the data in the stack is
available only on a LIFO basis using the methods shown
below.  Make the stack class capable of accommodating 
objects of any type. Throw an exception if the user 
attempts to pop an empty stack.

Methods:
isEmpty() -    returns true if this stack is empty. 
pop() -        Removes and returns the object from the top
               of this stack.
push(Object) - Pushes an item onto the top of this stack. 

This program was tested using JDK 1.1.3 under Win95.

The output from the program is:

Mary Jones
Sue Williams
Joe Johnson
Dick Baldwin
java.util.NoSuchElementException
**********************************************************/
import java.io.*;
import java.util.*;

//=======================================================//
class MyStack{
  //Note that this class contains and does not extend
  // Vector.
  private Vector stack = new Vector();
  //-----------------------------------------------------//

  boolean isEmpty(){
    return stack.isEmpty();
  }//end isEmpty()
  //-----------------------------------------------------//
  
  void push(Object item){
    stack.addElement(item);
  }//end push()
  //-----------------------------------------------------//
  
  Object pop()throws NoSuchElementException{
    Object temp = stack.lastElement();
    stack.removeElementAt((stack.size()-1));

//    stack.removeElementAt(stack.lastIndexOf(temp));
    return temp;
  }//end pop()
  //-----------------------------------------------------//
}//end class MyStack

//=======================================================//
class SampProg153{//controlling class
  public static void main(String[] args){
    MyStack stack = new MyStack();
    stack.push(new TestClass("Dick","Baldwin"));
    stack.push(new TestClass("Joe","Johnson"));
    stack.push(new TestClass("Sue","Williams"));
    stack.push(new TestClass("Mary","Jones"));

    try{
      while(!stack.isEmpty())
        System.out.println(stack.pop());
        
      //Try to pop an empty stack
      System.out.println(stack.pop());

    }catch(NoSuchElementException e){System.out.println(e);}
  }// end main
}//end class SampProg153 definition
//======================================================//

class TestClass{
  String first;
  String last;
  
  TestClass(String first, String last){//constructor
    this.first = first;
    this.last = last;
  }//end constructor
  //----------------------------------------------------//
  public String toString(){
    return first + " " + last;
  }//end toString()
}//end TestClass
//======================================================//
Q - Write a Java program that meets the following specifications.
 
/* File SampProg154.java Copyright 1998, R.G.Baldwin
From lesson 76

Without viewing the solution that follows, write a Java
application that uses the Vector class to implement a
LIFO structure similar to a stack.  

This structure has set and get methods that mirror the
typical push and pop methods in a stack.

However, this structure also has an enumerator that allows
you to access the individual elements in the structure
in sequential order, and to modify them in the process.

Make the structure class capable of accommodating 
objects of any type. Throw an exception if the user 
attempts to get an element from an empty structure.

Methods:
isEmpty() -   Returns true if the structure is empty. 
get() -       Removes and returns the object from the top
              of the structure.
set(Object) - Stores an element onto the top of the 
              structure. 

This program was tested using JDK 1.1.3 under Win95.

Typical output from the program is:
Set the structure
Enumerate and modify the structure
Dick Baldwin
Joe Johnson
Sue Williams
Mary Jones
Get the modified structure
Mary Modified
Sue Modified
Joe Modified
Dick Modified
java.util.NoSuchElementException
**********************************************************/
import java.io.*;
import java.util.*;

//=======================================================//
class MyStructure{
  private Vector structure = new Vector();
  //-----------------------------------------------------//

  boolean isEmpty(){
    return structure.isEmpty();
  }//end isEmpty()
  //-----------------------------------------------------//
  
  void set(Object item){
    structure.addElement(item);
  }//end set()
  //-----------------------------------------------------//
  
  Object get()throws NoSuchElementException{
    Object temp = structure.lastElement();
    structure.removeElementAt(structure.size()-1);
    return temp;
  }//end get()
  //-----------------------------------------------------//

  Enumeration getEnumerator(){
    return new Enumerator(this);
  }//end getEnumerator()
  //-----------------------------------------------------//
  
  int getSize(){
    return structure.size();
  }//end getSize()
  //-----------------------------------------------------//

  Object getElement(int which){
    return structure.elementAt(which);
  }//end getElement()
  //-----------------------------------------------------// 
}//end class MyStructure

//=======================================================//

class Enumerator implements Enumeration{
  MyStructure theStructure;
  int elementCounter;
  //-----------------------------------------------------//

  Enumerator(MyStructure theStructure){//constructor
    this.theStructure = theStructure;
    elementCounter = 0;
  }//end constructor
  //-----------------------------------------------------//
  public boolean hasMoreElements(){
    return (elementCounter < theStructure.getSize());
  }//end hasMoreElements
  //-----------------------------------------------------//

  public Object nextElement(){
    return theStructure.getElement(elementCounter++);
  }//end nextElement
}//end class Enumerator
//=======================================================//

class SampProg154{//controlling class
  public static void main(String[] args){
    MyStructure structure = new MyStructure();
    System.out.println("Set the structure");
    structure.set(new TestClass("Dick","Baldwin"));
    structure.set(new TestClass("Joe","Johnson"));
    structure.set(new TestClass("Sue","Williams"));
    structure.set(new TestClass("Mary","Jones"));
    
    System.out.println(
                   "Enumerate and modify the structure");
    Enumeration enum = structure.getEnumerator();
    
    TestClass temp;
    while(enum.hasMoreElements()){
      temp = (TestClass)enum.nextElement();
      //Display the element
      System.out.println(temp);
      //Modify the element
      temp.last = "Modified";
    }//end while loop
    
    System.out.println("Get the modified structure");
    try{
      while(!structure.isEmpty())
        System.out.println(structure.get());
        
      //Try to get element from an empty structure
      System.out.println(structure.get());

    }catch(NoSuchElementException e){
      System.out.println(e);
    }//end catch
  }// end main
}//end class SampProg154 definition
//=======================================================//

class TestClass{
  String first;
  String last;
  
  TestClass(String first, String last){//constructor
    this.first = first;
    this.last = last;
  }//end constructor
  //----------------------------------------------------//
  public String toString(){
    return first + " " + last;
  }//end toString()
}//end TestClass
//======================================================//
.

-end-