Previous Page TOC Index Next Page

Chapter 19

The utilities package

The Utilities package provides a collection of classes that implement various standard programming data structures. These classes are useful in a variety of ways and are the fundamental building blocks of the more complicated data structures used in the other Java packages and in your own applications. Unless otherwise noted, all of the interfaces and classes discussed in this chapter extend the java.lang.Object class. Table 19.1 lists the classes and interfaces implemented in this package along with a brief description of each.

Table 19.1. Utilities package interfaces and classes.

Interfaces
Enumeration Interface for classes that can enumerate a vector.
Observer Interface for classes that can observe observable objects.
.
Classes
BitSet Used to store a collection of binary values.
DateUsed to store date and time data.
Dictionary Used to store a collection of key and value pairs.
Hashtable Used to store a hash table.
Observable Used to store observable data.
Properties Used to store a properties list that can be saved.
Random Used to generate a pseudo-random number.
Stack Used to store a stack.
StringTokenizer Used to tokenize a string.
Vector Used to store a vector data type

Interfaces

The Utilities package has two interfaces that can be used in classes of your own design—Enumeration and Observer. Interfaces are a set of methods that must be written for any class that claims to “implement” the interface. This provides a consistent way of using all classes that implement the interface.

The Enumeration interface is used for classes that can retrieve data from a list, element by element. For example, there is an Enumeration class in the Utilities package that implements the Enumeration interface for use in conjunction with the Vector class. The Observer interface is useful in designing classes that can watch for changes that occur in other classes.

Enumeration

This interface specifies a set of methods used to enumerate—that is, iterate through—a list. An object that implements this interface may be used to iterate through a list only once because the Enumeration object is consumed through its use.

For example, an Enumeration object can be used to print all the elements of a Vector object, v, as follows:

for (Enumeration e=v.elements();e.hasMoreElements();)

    System.out.print(e.nextElement()+” “);

The Enumeration interface specifies only two methods—hasMoreElements() and nextElement(). The hasMoreElements() method must return True if there are elements remaining in the enumeration. The nextElement() method must return an object representing the next element within the object that is being enumerated. The details of how the Enumeration interface is implemented and how the data is represented internally are left up to the implementation of the specific class.

See also: Dictionary, Hashtable, Properties, Vector.

Observer

This interface, if implemented by a class, allows an object of the class to observe other objects of the class Observable. The observer is notified whenever the Observable object that it is watching has been changed.

The interface only specifies one method, update(Observable, Object). This method is called by the observed object to notify the observer of changes. A reference to the observed object is passed along with any additional object that the observed object wishes to pass to the observer. The first argument enables the observer to operate on the observed object, while the second argument is used to pass information from the observed to the observer.

Classes

The Utilities package supplies ten different classes that provide a wide variety of functionality. While these classes don’t generally have much in common, they all provide support for the most common data structures used by programmers.

BitSet

This class implements a data type that represents a collection of bits. The collection will grow dynamically as more bits are required. It is useful for representing a set of True/False values. Specific bits are identified using non-negative integers. The first bit is bit 0 (see Figure 19.1).

Figure FIGURE 19.1

Example of a BitSet object.

This class is most useful for storing a group of related True/False values such as user responses to Yes/No questions. Individual bits in the set are turned on or off with the set() and clear() methods, respectively. Individual bits are queried with the get() method. These methods all take the specific bit number as their only argument. The basic Boolean operations AND, OR, and XOR can be performed on two BitSets using the and(), or(), and xor() methods. Because these methods modify one of the BitSets, one generally will use the clone() method to create a duplicate of one, and then AND, OR, or XOR the clone with the second BitSet. The result of the operation then will end up in the cloned BitSet. The BitSet1 program in Listing 19.1 illustrates the basic BitSet operations, whereas Table 19.2 summarizes all the various methods available in the BitSet class.

import java.io.DataInputStream;

import java.util.BitSet;

class BitSet1 {

    public static void main(String args[])

        throws java.io.IOException

    {

        DataInputStream dis=new DataInputStream(System.in);

        String bitstring;

        BitSet set1,set2,set3;

        set1=new BitSet();

        set2=new BitSet();

        // Get the first bit sequence and store it

        System.out.println(“Bit sequence #1:”);

        bitstring=dis.readLine();

        for (short i=0;i<bitstring.length();i++){

            if (bitstring.charAt(i)==’1')

                set1.set(i);

            else

                set1.clear(i);

        }

        // Get the second bit sequence and store it

        System.out.println(“Bit sequence #2:”);

        bitstring=dis.readLine();

        for (short i=0;i<bitstring.length();i++){

            if (bitstring.charAt(i)==’1')

                set2.set(i);

            else

                set2.clear(i);

        }

        System.out.println(“BitSet #1: “+set1);

        System.out.println(“BitSet #2: “+set2);

        // Test the AND operation

        set3=(BitSet)set1.clone();

        set3.and(set2);

        System.out.println(“set1 AND set2: “+set3);

        // Test the OR operation

        set3=(BitSet)set1.clone();

        set3.or(set2);

        System.out.println(“set1 OR set2: “+set3);

        // Test the XOR operation

        set3=(BitSet)set1.clone();

        set3.xor(set2);

        System.out.println(“set1 XOR set2: “+set3);

    }

}

The output from this program looks like this:

Bit sequence #1:

1010

Bit sequence #2:

1100

BitSet #1: {0, 2}

BitSet #2: {0, 1}

set1 AND set2: {0}

set1 OR set2: {0, 1, 2}

set1 XOR set2: {1, 2}

Table 19.2. The BitSet interface.

Constructors
BitSet ( ) Constructs an empty BitSet.
BitSet(int) Constructs an empty BitSet of a given size.
Methods
and(BitSet) Logically ANDs the object’s bit set with another BitSet.
clear(int) Clears a specific bit.
clone() Creates a clone of the BitSet object.
equals(Object) Compares this object against another BitSet object.
get(int) Returns the value of a specific bit.
hashCode() Returns the hash code.
or(BitSet) Logically ORs the object’s bit set with another BitSet.
set(int) Sets a specific bit.
size() Returns the size of the set.
toString() Converts bit values to a string representation.
xor(BitSet) Logically XORs the object’s bit set with another BitSet.

In addition to extending the java.lang.Object class, BitSet implements the java.lang.Cloneable interface.

Date

This class is used to represent dates and times in a system-independent fashion. For example, the current date or a specific date can be printed as shown in Listing 19.2.

import java.util.Date;

public class Date1{

    public static void main (String args[]){

        Date today=new Date();

        System.out.println(“Today is “+today.toLocaleString()+

            “ (“+today.toGMTString()+”)”);

        Date birthday=new Date(89,10,14,8,30,00);

        System.out.println(“My birthday is”+

            birthday.toString()+” (“+birthday.toGMTString()+”)”);

        Date anniversary=new Date(“Jun 21, 1986”);

        System.out.println(“My anniversary is “+

            anniversary+” (“+anniversary.toGMTString()+”)”);

    }

}

The output from this program looks like this:

Today is 01/21/96 19:55:17 (22 Jan 1996 01:55:17 GMT)

My birthday is Thu Nov 14 08:30:00  1989 (14 Nov 1989 14:30:00 GMT)

My anniversary is Sat Jun 21 00:00:00  1989 (21 Jun 1986 05:00:00 GMT)

The default constructor is used when the current date and time are needed. A specific date and time can be used to initialize a Date object using the constructors that take three, five, and six integers. These constructors allow the date and time to be specified using YMD, YMDHM, or YMDHMS. Any parts of the time not specified by the three- and five-integer constructors will be set to zero.

NOTE
Date/time formats can be conveniently summarized using notations of the form YMD, YMDHMS, HMS, or MDY. These abbreviated formats indicate in what order the various numeric parts of the date will appear. Each letter refers to a specific component of the date/time: year (Y), month (M), day (D), hour (H), minute (M), and second (S). Whether the letter M refers to month or minute depends on the context.

Alternately, a Date object can be constructed using a single string that represents a date and time using a variety of different syntax. One of the most important is the international standard date syntax of the form, “Sun, 14 Aug 1995 9:00:00 GMT.” Continental U.S. time zone abbreviations are understood, but time zone offsets should be considered for general use—for example, “Sun, 14 Aug 1995 9:00:00 GMT+0600” (six hours west of the Greenwich meridian). The local time zone is assumed if none is supplied.

NOTE
This class intends to store date and time information in UTC (Coordinated Universal Time). However, it does not necessarily achieve this goal exactly. The implementation of the class is limited by the underlying time system of the operating system. Because modern operating systems typically assume that a day is always 86,400 seconds, the extra leap seconds that are needed about once a year to accurately reflect UTC usually are not added. UTC is a time standard based on an atomic clock. Time specifications using UTC are considered equal to GMT (Greenwich Mean Time).

The date can be converted to a text representation using the methods toString(), toGMTString(), and toLocaleString(), which convert the date and time to the standard UNIX, GMT, or local time formats, respectively. When a date is being converted to a string by an automatic coercion, the toString() method will be used.

The Date class also has methods for setting and querying the date and time component values once the Date object is constructed. The individual parts of the date (month, date, year) and time (hours, minutes, seconds) always are specified in local time. When referring to the various parts of the date and time, the first letter of each part typically is used as an abbreviation. For example, YMDHMS would be used to indicate that all six parts (year, month, date, hour, minute, second) are present. Each of these parts of the date and time have a specific range of acceptable values, as illustrated in Table 19.3.

Year Year minus 1900
Month 0-11 (January=0)
Date 1-31
Day 0-6 (Sunday=0)
Hour 0-23
Minute 0-59
Second 0-59

The date and time also can be specified using a single integer UTC value that represents the number of milliseconds that have elapsed since a specific starting date (which might vary from system to system). For UNIX systems this date is January 1, 1970. The program Date2 in Listing 19.3 shows how this single value corresponds to the normal YMDHMS representation.

import java.util.Date;

public class Date2{

    public static void main (String args[]){

        Date beginning=new Date(0);

        Date anniversary=new Date(“Jun 21, 1986”);

        Date today=new Date();

        System.out.println(beginning+”=”+beginning.getTime());

        System.out.println(anniversary+”=”+anniversary.getTime());

        System.out.println(today+”=”+today.getTime());

    }

}

The output from this program looks like this:

Wed Dec 31 18:00:00  1969=0

Sat Jun 21 00:00:00  1986=519714000000

Sun Jan 21 19:55:17  1996=822275717000

Dates can be compared to each other by using this UTC value or by using the methodsafter(), before(), or equals().

Table 19.4 summarizes the complete interface of the Date class.

Table 19.4. The Date interface.

Constructors
Date() Constructs a date using today’s date and time.
Date(long) Constructs a date using a single UTC value.
Date(int, int, int) Constructs a date using YMD.
Date(int, int, int, int, int) Constructs a date using YMDHM.
Date(int, int, int, int, int, int) Constructs a date using YMDHMS.
Date(string) Constructs a date from a string.
Static Methods
UTC(int, int, int, int, int, int) Calculates a UTC value from YMDHMS.
parse(string) Returns the single UTC value of a date in text format.
Methods
after(Date) True if the date is later than the specified date.
before(Date) True if the date is earlier than the specified date.
equals(Object) True if the date and the specified date are equal.
getDate() Returns the day of the month.
getDay() Returns the day of the week.
Hours() Returns the hour.
inutes() Returns the minute.
onth() Returns the month.
econds() Returns the second.
ime() Returns the time as a single UTC value.
imezoneOffset() Returns the time zone offset, in minutes, for this locale.
ear() Returns the year after 1900.
hashcode() Computes a hash code for the date.
setDate(int) Sets the date.
setHours(int) Sets the hours.
setMinutes(int) Sets the minutes.
setMonth(int) Sets the month.
setSeconds(int) Sets the seconds.
setTime(long) Sets the time using a single UTC value.
setYear(int) Sets the year.
toGMTString() Converts a date to text using Internet GMT conventions.
toLocaleString() Converts a date to text using locale conventions.
toString() Converts a date to text using UNIX ctime() conventions.

Random

This class implements a pseudo-random number data type used to generate a stream of seemingly random numbers. To create a sequence of different pseudo-random values each time the application is run, create the Random object as follows:

Random r=new Random();

This will seed the random generator with the current time. On the other hand, consider the following statement:

Random r=new Random(326);    // Pick any value

This will seed the random generator with the same value each time, resulting in the same sequence of pseudo-random numbers each time the application is run. The generator can be reseeded at any time using the setSeed() method.

Pseudo-random numbers can be generated by using one of the functions: nextInt(), nextLong(), nextFloat(), nextDouble(), or nextGaussian(). For example, the program Random1 in Listing 19.4 will print out five pseudo-random uniformly distributed values using these functions.

import java.lang.Math;

import java.util.Date;

import java.util.Random;

class Random1 {

    public static void main(String args[])

        throws java.io.IOException

    {

        int count=6;

        Random randGen=new Random();

        System.out.println(“Uniform Random Integers”);

        for (int i=0;i<count;i++)

        System.out.print(randGen.nextInt()+” “);

        System.out.println(“\n”);

        System.out.println(“Uniform Random Floats”);

        for (int i=0;i<count;i++)

        System.out.print(randGen.nextFloat()+” “);

        System.out.println(“\n”);

        System.out.println(“Gaussian Random Floats”);

        for (int i=0;i<count;i++)

            System.out.print(randGen.nextGaussian()+” “);

        System.out.println(“\n”);

        System.out.println(“Uniform Random Integers [1,6]”);

        for (int i=0;i<count;i++)

            System.out.print((Math.abs(randGen.nextInt())%6+1)+” “);

        System.out.println(“\n”);

        }

}

The output from the preceding program looks like this:

Uniform Random Integers

1704667569 -1431446235 1024613888 438489989 710330974 -1689521238

Uniform Random Floats

0.689189 0.0579988 0.0933537 0.748228 0.400992 0.222109

Gaussian Random Floats

-0.201843 -0.0111578 1.63927 0.205938 -0.365471 0.626304

Uniform Random Integers [1,6]

4 6 1 6 3 2

If you need to generate uniformly distributed random integers within a specific range, the output from nextInt(), nextLong(), or nextDouble() can be scaled to match the required range. A simpler approach is to take the remainder of the result of nextInt() divided by the number of different values plus the first value of the range. For example, if the values 10 to 20 are needed one can use the formula nextInt()%21+10. Unfortunately, though this method is much simpler than scaling the output of nextInt(), it only is guaranteed to work on truly randomvalues. Because the pseudo-random generator might have various undesired correlations, the modulus operator might not provide acceptable results—one might get all odd numbers, for example.

NOTE
The uniformly distributed random numbers are generated using a modified linear congruential method with a 48-bit seed. Uniformly distributed random numbers within a given range will all appear with the same frequency. This class can also generate random numbers from a Gaussian or Normal distribution. The Gaussian frequency distribution curve is also referred to as a bell curve. For information on this, see Donald Knuth, The Art of Computer Programming, Volume 2, Section 3.2.1.

Table 19.5 summarizes the complete interface of the Random class.

Table 19.5. The Random interface.

Constructors
Random() Creates a new random number generator.
Random(long) Creates a new random number generator using a seed.
Methods
nextDouble() Returns a pseudo-random uniformly distributed Double.
nextFloat() Returns a pseudo-random uniformly distributed Float.
nextGaussian() Returns a pseudo-random Gaussian distributed Double.
nextInt() Returns a pseudo-random uniformly distributed Int.
nextLong() Returns a pseudo-random uniformly distributed Long.
setSeed(long) Sets the seed of the pseudo-random number generator.

Refer also to Random().

StringTokenizer

The StringTokenizer class breaks up a string into tokens. The delimiter set can be specified when the StringTokenizer object is created or can be specified on a per-token basis. The default delimiter set is the set of whitespace characters. For example, the StringTokenizer1 code in Listing 19.5 prints out each word of the string on a separate line.

import java.io.DataInputStream;

import java.util.StringTokenizer;

class StringTokenizer1 {

    public static void main(String args[])

        throws java.io.IOException

    {

        DataInputStream dis=new DataInputStream(System.in);

        System.out.println(“Enter a sentence: “);

        String s=dis.readLine();

        StringTokenizer st=new StringTokenizer(s);

        while (st.hasMoreTokens())

            System.out.println(st.nextToken());

    }

}

The output from this listing will look like this:

Enter a sentence:

Four score and seven

Four

score

and

seven

The method countTokens() returns the number of tokens remaining in the string using the current delimiter set—that is, the number of times nextToken() can be called before generating an exception. This is an efficient method because it does not actually construct the substrings that nextToken() must generate.

In addition to extending the java.lang.object class, the StringTokenizer class implements the java.util.Enumeration interface. Table 19.6 summarizes the methods of the StringTokenizer class.

Table 19.6. The StringTokenizer interface.

Constructors
StringTokenizer(string) Constructs a StringTokenizer given a string using whitespace as delimiters.
StringTokenizer(string, string) Constructs a StringTokenizer given a string and a delimiter set.
StringTokenizer(string, string, boolean) Constructs a StringTokenizer given a string and a delimiter set.
Methods
countTokens() Returns the number of tokens remaining in the string.
hasMoreTokens() Returns True if more tokens exist.
nextToken() Returns the next token of the string.
nextToken(string) Returns the next token, given a new delimiter set.
hasMoreTokens() Returns True if more elements exist in the enumeration.
nextElement() Returns the next element of the enumeration using the currentdelimiter set.

Vector

The Vector class implements a dynamically allocated list of objects. It attempts to optimize storage by increasing the storage capacity of the list when needed by increments larger than just one object. With this mechanism, there typically is some excess capacity in the list. When this capacity is exhausted, the list is reallocated to add another block of objects at the end of the list. Setting the capacity of the Vector object to the needed size before inserting a large number of objects will reduce the need for incremental reallocation. Because of this mechanism, it is important to remember that the capacity (available elements in the Vector object) and the size (number of elements currently stored in the Vector object) usually are not the same.

For example, in Figure 19.2, a Vector with capacityIncrement equal to three has been created. As objects are added to the Vector, new space is allocated in chunks of three objects. After five elements have been added, there still will be room for one more element without the need for any additional memory allocation. After the sixth element has been added, there is no more excess capacity. When the seventh element is added, a new allocation will be made that adds three additional elements, giving a total capacity of nine. After the seventh element is added, there will be two remaining unused elements.

Figure FIGURE 19.2.

Vector objects with varying number of elements.

The initial storage capacity and the capacity increment both can be specified in the constructor. Even though the capacity is automatically increased as needed, the ensureCapacity() method can be used to increase the capacity to a specific minimum number of elements, whereas trimToSize() can be used to reduce the capacity to the minimum needed to store the current elements. New elements can be added to the Vector using the addElement() and insertElementAt() methods. The elements passed to be stored in the Vector must be derived from type Object. Elements can be changed using the setElementAt() method. Removal of elements is accomplished with the removeElement(), removeElementAt(), and removeAllElements() methods. Elements can be accessed directly using the elementAt(), firstElement(), and lastElement() methods, whereas elements can be located using the indexOf() and lastIndexOf() methods. Information about the size and the capacity of the Vector are returned by the size() and capacity() methods respectively. The setSize() method can be used to directly change the size of the Vector.

For example, the Vector1 code in Listing 19.6 creates a Vector of integers by adding new elements to the end. It then, using a variety of techniques, prints the Vector.

import java.lang.Integer;

import java.util.Enumeration;

import java.util.Vector;

class Vector1 {

    public static void main(String args[]){

        Vector v=new Vector(10,10);

        for (int i=0;i<20;i++)

            v.addElement(new Integer(i));

        System.out.println(“Vector in original order using an Enumeration”);

        for (Enumeration e=v.elements();e.hasMoreElements();)

            System.out.print(e.nextElement()+” “);

        System.out.println();

        System.out.println(“Vector in original order using elementAt”);

        for (int i=0;i<v.size();i++)

            System.out.print(v.elementAt(i)+” “);

        System.out.println();

        // Print out the original vector

        System.out.println(“\nVector in reverse order using elementAt”);

        for (int i=v.size()-1;i>=0;i--)

            System.out.print(v.elementAt(i)+” “);

        System.out.println();

        // Print out the original vector

        System.out.println(“\nVector as a String”);

        System.out.println(v.toString());

    }

}

The output from this program looks like this:

Vector in original order using an Enumeration

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

Vector in original order using elementAt

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

Vector in reverse order using elementAt

19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

Vector as a String

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
NOTE
The expression new Integer() was used to create integer objects to store because the fundamental types, such as int, are not objects in Java. This technique is used many times throughout this chapter.

Notice the use of the Enumeration object as one way to access the elements of a Vector. Look at the following lines:

for (Enumeration e=v.elements();e.hasMoreElements();)

    System.out.print(e.nextElement()+” “);

One can see that an Enumeration object that represents all of the elements in the Vector is created and returned by the Vector method elements(). With this Enumeration object, the loop can check to see if there are more elements to process using the Enumeration method hasMoreElements() and can get the next element in the Vector using the Enumeration method nextElement().

The Vector2 program in Listing 19.7 illustrates some of the vector-accessing techniques. It first generates a vector of random integers, then allows the user to search for a specific value. The location of the first and last occurrences of the value is printed by the program using the indexOf() and lastIndexOf() methods.

import java.io.DataInputStream;

import java.lang.Integer;

import java.lang.Math;

import java.util.Enumeration;

import java.util.Random;

import java.util.Vector;

class Vector2 {

    public static void main(String args[])

        throws java.io.IOException

    {

        int numElements;

        DataInputStream dis=new DataInputStream(System.in);

        Vector v=new Vector(10,10);

        Random randGen=new Random();

        System.out.println(“How many random elements? “);

        numElements=Integer.valueOf(dis.readLine()).intValue();

        for (int i=0;i<numElements;i++)

            v.addElement(new Integer(Math.abs(

                randGen.nextInt())%numElements));

        System.out.println(v.toString());

        Integer searchValue;

        System.out.println(“Find which value? “);

        searchValue=Integer.valueOf(dis.readLine());

        System.out.println(“First occurrence is element “+

            v.indexOf(searchValue));

        System.out.println(“Last occurrence is element “+

            v.lastIndexOf(searchValue));

    }

}

The output from this program looks like this:

How many random elements?

10

[0, 2, 8, 4, 9, 7, 8, 6, 3, 2]

Find which value?

8

First occurrence is element 2

Last occurrence is element 6

In addition to extending the java.lang.Object class, the Vector class implements the java.lang.Cloneable interface. Table 19.7 summarizes the methods of the Vector class.

Table 19.7. Vector interface.

Variables
capacityIncrement Size of the incremental allocations, in elements.
elementCount Number of elements in Vector.
elementData Buffer where the elements are stored.
Constructors
Vector() Constructs an empty vector.
Vector(int) Constructs an empty vector with the specified storage capacity.
Vector(int, int) Constructs an empty vector with the specified storage capacity and capacityIncrement.
Methods
addElement(Object) Adds the specified object at the end of the Vector.
capacity( ) Returns the capacity of the Vector.
clone( ) Creates a clone of the Vector.
contains(Object) True if the specified object is in the Vector.
copyInto(Object[]) Copies the elements of this vector into an array.
elementAt(int) Returns the element at the specified index.
elements( ) Returns an Enumeration of the elements.
ensureCapacity(int) Ensures that the Vector has the specified capacity.
firstElement( ) Returns the first element of the Vector.
indexOf(Object) Returns the index of the first occurrence of the specified object within the Vector.
indexOf(Object, int) Returns the index of the specified object within the Vector starting the search at the index specified and proceeding toward the end of the Vector.
insertElementAt(Object, int) Inserts an object at the index specified.
isEmpty( ) True if the Vector is empty.
lastElement( ) Returns the last element of the Vector.
lastIndexOf(Object) Returns the index of the last occurrence of the specified object within the Vector.
lastIndexOf(Object, int) Returns the index of the specified object within the Vector starting the search at the index specified and proceeding toward the beginning of the Vector.
removeAllElements( ) Removes all elements of the Vector.
removeElement(Object) Removes the specified object from the Vector.
removeElementAt(int) Removes the element with the specified index.
setElementAt(Object, int) Stores the object at the specified index in the Vector.
setSize(int) Sets the size of the Vector.
size( ) Returns the number of elements in the Vector.
toString( ) Converts the Vector to a string.
trimToSize( ) Trims the Vector’s capacity down to the specified size.

Refer also to Vector, Hashtable.

Stack

This class implements a Last In, First Out (LIFO) stack of objects. Even though it is based on (extends) the Vector class, Stacks are typically not accessed in a direct fashion. Instead, values are pushed onto and popped off of the top of the stack. The net effect is that values that were most recently pushed are the first ones to be popped. For example, the Stack1 code in Listing 19.8 pushes strings onto the stack, and then retrieves them. The strings will end up being printed in the reverse order that they were stored.

import java.io.DataInputStream;

import java.util.Stack;

import java.util.StringTokenizer;

class Stack1 {

    public static void main(String args[])

        throws java.io.IOException

    {

        DataInputStream dis=new DataInputStream(System.in);

        System.out.println(“Enter a sentence: “);

        String s=dis.readLine();

        StringTokenizer st=new StringTokenizer(s);

        Stack stack=new Stack();

        while (st.hasMoreTokens())

            stack.push(st.nextToken());

        while (!stack.empty())

            System.out.print((String)stack.pop()+” “);

        System.out.println();

    }

}

The output from this program looks like this:

Enter a sentence:

The quick brown fox jumps over the lazy dog

dog lazy the over jumps fox brown quick The

Even though Stack objects normally are not accessed in a direct fashion, it is possible to search the Stack for a specific value using the search() method. It accepts an object to find and returns the distance from the top of the Stack where the object was found. It will return -1 if the object is not found.

The method peek() will return the top object on the Stack without actually removing it from the Stack. The peek() method will throw an EmptyStackException if the Stack has no items.

Table 19.8 summarizes the complete interface of the Stack class.

Table 19.8. Stack interface.

Constructors
Stack( ) Constructs an empty Stack.
Methods
empty( ) True if the Stack is empty.
peek( ) Returns the top object on the Stack.
pop( ) Pops an element off the Stack.
push(Object) Pushes an element onto the Stack.
search(Object) Finds an object on the Stack.

Dictionary

This class is an abstract class that is used as a base for the Hashtable class. It implements a data structure that allows a collection of key and value pairs to be stored. Any type of object can be used for the keys or the values. Typically, the keys are used to find a particular corresponding value. Figure 19.3 illustrates a Dictionary where product codes and names are stored.

Figure FIGURE 19.3.

Example of a Dictionary object.

Because this class is an abstract class that cannot be used directly, the code examples presented cannot actually be run. They are presented only to illustrate the purpose and use of the methods declared by this class. The following code would, hypothetically, be used to create aDictionary with these values illustrated:

Dictionary products = new Dictionary();

products.put(new Integer(342), “Widget”);

products.put(new Integer(124), “Gadget”);

products.put(new Integer(754), “FooBar”);

The put() method is used to insert a key and value pair into the Dictionary. The two arguments both must be derived from the class Object. The key is the first argument and the value is the second.

A value can be retrieved using the get() method and a specific key to find. It returns the null value if the specified key is not found. For example:

String name = products.get(new Integer(124));

if (name != null) {

    System.out.println(“Product name for code 124 is “ + name);

}

While an individual object can be retrieved with the get() method, sometimes it is necessary to access all of the keys or all of the values. There are two methods, keys() and elements(), that will return Enumerations that can be used to access the keys and the values, respectively.

Table 19.9 summarizes the complete interface of the Dictionary class.

Table 19.9. Dictionary interface.

Constructors
Dictionary( ) Constructs an empty Dictionary.
Methods
elements ( ) Returns an Enumeration of the values.
get(Object) Returns the object associated with the specified key.
isEmpty( ) True if the Dictionary has no elements.
keys( ) Returns an Enumeration of the keys.
put(Object, Object) Stores the specified key and value pair in the Dictionary.
remove(Object) Removes an element from the Dictionary by its key.
size( ) Returns the number of elements stored.

Refer also to Enumeration, Hashtable, Properties.

Hashtable

This class implements a hash table storage mechanism for storing key and value pairs. Hash tables are designed to quickly locate and retrieve information stored using a key. Keys and values may be of any object type but the key object’s class must implement the hashCode() and equals() methods.

The example Hashtable1 in Listing 19.9 creates a Hashtable object and stores 10 key and value pairs using the put() method. It then uses the get() method to return the value corresponding to a key entered by the user.

import java.io.DataInputStream;

import java.lang.Integer;

import java.lang.Math;

import java.util.Random;

import java.util.Hashtable;

class Hashtable1 {

    public static void main(String args[])

        throws java.io.IOException

    {

        DataInputStream dis=new DataInputStream(System.in);

        int numElements=10;

        String keys[]={“Red”,”Green”,”Blue”,”Cyan”,”Magenta”,

            “Yellow”,”Black”,”Orange”,”Purple”,”White”};

        Hashtable ht;

        Random randGen=new Random();

        ht=new Hashtable(numElements*2);

        for (int i=0;i<numElements;i++)

            ht.put(keys[i],new Integer(Math.abs(

                randGen.nextInt())%numElements));

        System.out.println(ht.toString());

        String keyValue;

        System.out.println(“Which key to find? “);

        keyValue=dis.readLine();

        Integer value=(Integer)ht.get(keyValue);

        if (value!=null) System.out.println(keyValue+” = “+value);

    }

}

The output from this program looks like this:

{Cyan=4, White=0, Magenta=4, Red=5, Black=3, Green=8, Purple=3, Orange=4, Yellow=2, ÂBlue=6}

Which key to find?

Red

Red = 5

In addition to the get() method, the contains() and containsKey() methods can be used to search for a particular value or key respectively. Both return True or False depending on whether or not the search was successful. The contains() method must perform an exhaustive search of the table and is not as efficient as the containsKey() method, which can take advantage of the hash table’s storage mechanism to find the key quickly.

Because hash tables need to allocate storage for more data than actually is stored, a measurement called the load factor is used to indicate the number of used storage spaces as a fraction of the total available storage spaces. It is expressed as a value between 0 and 100 percent. Typically the load factor should not be higher than about 50 percent for efficient retrieval of data from a hash table. When specifying the load factor in a program, a fractional value in the range 0.0 to 1.0 should be used to represent load factors in the range 0 to 100 percent.

Hash tables can be constructed in three different ways: by specifying the desired initial capacity and load factor, by specifying only the initial capacity, or by specifying neither. If the load factor is not specified, the Hashtable will be rehashed into a larger table when it is full; otherwise it is rehashed when it exceeds the load factor. The constructors will throw an IllegalArgumentException if the initial capacity is less than or equal to zero, or if the load factor is less than or equal to zero.

The clone() method can be used to create a copy (clone) of the Hashtable. However, it creates a shallow copy of the Hashtable—that is, the keys and values themselves are not clones. It overrides the inherited clone() method.

CAUTION
The clone() method is a relatively expensive operation to perform in terms of memory utilization and execution time. Because the new Hashtable still refers directly to the objects (keys and values) stored in the old table, caution should be used to avoid making changes that will disrupt the original Hashtable.

The Hashtable class extends the java.util.Dictionary class and implements the java.lang.Cloneable interface. Table 19.10 summarizes the methods of the Hashtable class.

Table 19.10. The Hashtable interface.

Constructors
Hashtable( ) Constructs an empty Hashtable.
Hashtable(int) Constructs an empty Hashtable with the specified capacity.
Hashtable(int, float) Constructs an empty Hashtable given capacity and load factor.
Methods
clear( ) Deletes all elements from the Hashtable.
clone( ) Creates a clone of the Hashtable.
contains(Object) True if the specified object is an element of the Hashtable.
containsKey(Object) True if the Hashtable contains the specified key.
elements( ) Returns an Enumeration of the Hashtable’s values.
get(Object) Returns the object associated with the specified key.
isEmpty( ) True if the Hashtable has no elements.
keys( ) Returns an Enumeration of the keys.
put(Object, Object) Stores the specified key and value pair in the Hashtable.
rehash( ) Rehashes the contents of the table into a bigger table.
remove(Object) Removes an element from the Hashtable by its key.
size( ) Returns the number of elements stored.
toString( ) Converts the contents to a very long string.

Refer also to hashCode, equals.

Properties

This class is a Hashtable that can be stored and restored from a stream. It is used to implement persistent properties. It also allows for an unlimited level of nesting by searching a default property list if the required property is not found.

The example program Properties1 in Listing 19.10 creates two Properties lists. One will be the default property list and the other will be the user-defined property list. When the user property list is created, the default Properties object is passed. When the user property list is searched, if the key value is not found, the default Properties list will be searched.

import java.io.DataInputStream;

import java.lang.Integer;

import java.util.Properties;

class Properties1 {

    public static void main(String args[])

        throws java.io.IOException

    {

        int numElements=4;

        String defaultNames[]={“Red”,”Green”,”Blue”,”Purple”};

        int defaultValues[]={1,2,3,4};

        String userNames[]={“Red”,”Yellow”,”Orange”,”Blue”};

        int userValues[]={100,200,300,400};

        DataInputStream dis=new DataInputStream(System.in);

        Properties defaultProps=new Properties();

        Properties userProps=new Properties(defaultProps);

        for (int i=0;i<numElements;i++){

            defaultProps.put(defaultNames[i],

                Integer.toString(defaultValues[i]));

            userProps.put(userNames[i],

                Integer.toString(userValues[i]));

        }

        System.out.println(“Default Properties”);

        defaultProps.list(System.out);

        System.out.println(“\nUser Defined Properties”);

        userProps.list(System.out);

        String keyValue;

        System.out.println(“\nWhich property to find? “);

        keyValue=dis.readLine();

        System.out.println(“Property ‘“+keyValue+”’ is ‘“+

            userProps.getProperty(keyValue)+”’”);

    }

}

Notice that the getProperties() method is used instead of the inherited get() method. The get() method only searches the current Properties object. The getProperties() method must be used in order to have the default Properties list searched. An alternative form of the getProperties() method has a second argument that is a default Properties list to search instead of the default specified when the Properties object was created.

The propertyNames() method can be used to return an Enumeration that can be used to index through all of the property names. This Enumeration includes the property names from the default Properties list. Likewise, the list() method, which prints the Properties list tothe standard output, will list all of the properties of the current Properties object and thosein the default Properties object.

Properties objects can be written to and read from a stream using the save() and load() methods respectively. In addition to the output or input stream, the save method has an additional string argument that will be written to the beginning of the stream as a header comment.

The Properties class extends the Hashtable class. Table 19.11 summarizes the methods of the Properties class.

Table 19.11. The Properties interface.

Variables
defaults Default Properties list to search.
Constructors
Properties( ) Constructs an empty property list.
Properties(Properties) Constructs an empty property list with specified default.
.
Methods
getProperty(string) Returns a property given the key.
getProperty(string, string) Returns a property given the specified key and default.
list(PrintStream) Lists the properties to a stream for debugging.
load(InputStream) Reads the properties from an InputStream.
propertyNames( ) Returns an Enumeration of all of the keys.
save(OutputStream, string) Writes the properties to an OutputStream.

Observable

This class acts as a base class for objects that wish to be observed by other objects that implement the Observer interface. An Observable object can notify its Observers whenever theObservable object is modified using the notifyObservers() method. This method accomplishes the notification by invoking the update() method of all of its Observers, optionally passing a data object which is passed to notifyObservers. Observable objects may have any number of Observers.

Table 19.12 summarizes the complete interface of the Observable class.

Table 19.12. Observable interface.

Constructors
Observable( )
Methods
addObserver(Observer) Adds an Observer to the observer list.
clearChanged( ) Clears an observable change.
countObservers( ) Returns the number of Observers.
deleteObserver(Observer) Deletes an Observer from the observer list.
deleteObservers( ) Deletes all Observers from the observer list.
hasChanged( ) True if an observable change occurred.
notifyObservers( ) Notifies all observers if an observable change occurred.
notifyObservers(Object) Notifies all observers of a specific observable change.
setChanged( ) Sets a flag to indicate that an observable change occurred.

Refer also to Observer.

Summary

This chapter has described the classes that make up the Java Utilities package. This package provides complete implementations of some of the most useful data types (other than the fundamental numeric types) and the basic data structures needed by programmers. Many of the data types and data structures that you will develop using Java will be based on the classes found in the Utilities package. This chapter should be a good starting point for understanding the utility of these important Java classes and for understanding how to use them effectively.


Previous Page TOC Index Next Page