In this chapter you'll learn how to work with all the useful utility classes contained in the java.util package. You'll learn to use the Date class to manipulate Date objects, to generate random numbers using the Random class, and to work with data structures such as dictionaries, stacks, hash tables, vectors, and bit sets. When you finish this chapter you'll be able to make productive use of these utility classes in your own programs.
The Date class encapsulates date and time information and allows Date objects to be accessed in a system-independent manner. Date provides methods for accessing specific date and time measurements, such as year, month, day, date, hours, minutes, and seconds, and for displaying dates in a variety of standard formats.
The Date class provides six constructors for creating Date objects. The default constructor creates a Date object with the current system date and time. Other constructors allow Date objects to be set to other dates and times. The access methods defined by the Date class support comparisons between dates and provide access to specific date information, including the time zone offset.
If you intend to process date-related information in a program, you should consult the API page for the Date class to obtain a full list of the available Date methods.
The DateApp program illustrates the use of the Date class. It shows how Date objects are created and manipulated using the methods provided by the class. (See Listing 14.1.)
Listing 14.1. The source code of the DateApp program.import java.lang.System;
import java.util.Date;
public class DateApp {
public static void main(String args[]){
Date today = new Date();
String days[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
System.out.println("Today's date (locale conventions): "+
today.toLocaleString());
System.out.println("Today's date (Unix conventions): "+today.toString());
System.out.println("Today's date (GMT conventions): "+today.toGMTString());
System.out.println("Year: "+today.getYear());
System.out.println("Month: "+today.getMonth());
System.out.println("Date: "+today.getDate());
System.out.println("Day: "+days[today.getDay()]);
System.out.println("Hour: "+today.getHours());
System.out.println("Minute: "+today.getMinutes());
System.out.println("Second: "+today.getSeconds());
Date newYears2000 = new Date(100,0,1);
System.out.println("New Years Day 2000: "+newYears2000.toString());
System.out.println("New Years Day 2000 is "+days[newYears2000.getDay()]);
}
}
The program creates a Date object using the default constructor and assigns it to the today variable. It then declares an array named days and initializes it to the three-letter abbreviations of the days of the week. The toLocaleString(), toString(), and toGMTString() methods are used to convert and print the date using the local operating system and standard UNIX and GMT conventions.
The various get methods supported by the Date class are used to print out the year, month, date, day, hours, minutes, and seconds corresponding to the current date.
A new Date object is constructed and assigned to the newYears2000 variable. The Date constructor takes the arguments 100, 0, and 1. They specify the 100th year after the year 1900, the 0th month (that is, January), and the first day of the month. The date specified is New Year's Day in the year 2000, as you probably guessed from the name of the variable in which it is stored.
The newYears2000 date is printed followed by the actual day of the week corresponding to the date.
The output of the DateApp program is as follows. When you run the program, you will obviously get a different date for the first part of the program's processing. The following are the results that were displayed when I ran the program:
Today's date (locale conventions): 03/01/96 09:53:46
Today's date (Unix conventions): Fri Mar 01 09:53:46 1996
Today's date (GMT conventions): 1 Mar 1996 17:53:46 GMT
Year: 96
Month: 2
Date: 1
Day: Fri
Hour: 9
Minute: 53
Second: 46
New Years Day 2000: Sat Jan 01 00:00:00 2000
New Years Day 2000 is Sat
The Random class provides a template for the creation of random number generators. It differs from the random() method of the java.lang.Math class in that it allows any number of random number generators to be created as separate objects. The Math.random() method provides a static function for the generation of random double values. This static method is shared by all program code.
Objects of the Random class generate random numbers using a linear congruential formula. Two constructors are provided for creating Random objects. The default constructor initializes the seed of the random number generator using the current system time. The other constructor allows the seed to be set to an initial long value.
The Java API page says the random number generator uses a 48-bit seed, but the constructor allows only a 32-bit value to be passed as a seed. In any case, the random number generators that are created as objects of this class should be sufficient for most random number generation needs.
The Random class provides six access methods, five of which are used to generate random values. The nextInt(), nextLong(), nextFloat(), and nextDouble() methods generate values for the numeric data types. The values generated by nextFloat() and nextDouble() are between 0.0 and 1.0. The nextGaussian() method generates a Gaussian distribution of double values with mean 0.0 and standard deviation 1.0.
The setSeed() method is used to reset the seed of the random number generator.
The RandomApp program demonstrates the use of the Random class. (See Listing 14.2.) It creates an object of class Random using the default constructor and assigns it to r. This causes the random number generator to be seeded using the current system time. Three for loops are used to print random int, double, and Gaussian distributed double values. Each loop prints four values.
Listing 14.2. The source code of the RandomApp program.import java.lang.System;
import java.util.Random;
public class RandomApp {
public static void main(String args[]){
Random r = new Random();
for(int i=0;i<4;++i) System.out.print(r.nextInt()+" ");
System.out.println();
r = new Random(123456789);
for(int i=0;i<4;++i) System.out.print(r.nextDouble()+" ");
System.out.println();
r.setSeed(234567890);
for(int i=0;i<4;++i) System.out.print(r.nextGaussian()+" ");
System.out.println();
}
}
The output generated by the program when it was run on my computer produced the following results:
1799702397 -2014911382 618703884 -1181330064
0.664038 0.456952 0.390506 0.893341
0.113781 0.412296 -1.57262 0.0756829
It will produce different results when it is run on your computer because the first line that is printed uses the Random() constructor to generate the output data.
The Enumeration interface specifies two methods to be used to index through a set of objects or values: hasMoreElements() and nextElement(). The hasMoreElements() method enables you to determine whether more elements are contained in an Enumeration object. The nextElement() method returns the next element contained by an object.
Enumeration-implementing objects are said to be consumed by their use. This means that the Enumeration objects cannot be restarted to reindex through the elements they contain. Their elements may be accessed only once.
The Enumeration interface is implemented by the StringTokenizer class as discussed later in this chapter in the section "The StringTokenizer Class." It is also used to obtain a list of elements contained in a vector, as shown in the programming example in the next section.
The Vector class provides the capability to implement a growable array. The array grows larger as more elements are added to it. The array may also be reduced in size, after some of its elements have been deleted. This is accomplished using the trimToSize() method.
Vector operates by creating an initial storage capacity and then adding to this capacity as needed. It grows by an increment defined by the capacityIncrement variable. The initial storage capacity and capacityIncrement can be specified in Vector's constructor. A second constructor is used when you want to specify only the initial storage capacity. A third, default constructor specifies neither the initial capacity nor the capacityIncrement. This constructor lets Java figure out the best parameters to use for Vector objects.
The access methods provided by the Vector class support array-like operations and operations related to the size of Vector objects. The array-like operations allow elements to be added, deleted, and inserted into vectors. They also allow tests to be performed on the contents of vectors and specific elements to be retrieved. The size-related operations allow the byte size and number of elements of the vector to be determined and the vector size to be increased to a certain capacity or trimmed to the minimum capacity needed. Consult the Vector API page for a complete description of these methods.
The VectorApp program illustrates the use of vectors and the Enumeration interface. (See Listing 14.3.)
Listing 14.3. The source code of the VectorApp program.import java.lang.System;
import java.util.Vector;
import java.util.Enumeration;
public class VectorApp {
public static void main(String args[]){
Vector v = new Vector();
v.addElement("one");
v.addElement("two");
v.addElement("three");
v.insertElementAt("zero",0);
v.insertElementAt("oops",3);
v.insertElementAt("four",5);
System.out.println("Size: "+v.size());
Enumeration enum = v.elements();
while (enum.hasMoreElements())
System.out.print(enum.nextElement()+" ");
System.out.println();
v.removeElement("oops");
System.out.println("Size: "+v.size());
for(int i=0;i<v.size();++i)
System.out.print(v.elementAt(i)+" ");
System.out.println();
}
}
The program creates a Vector object using the default constructor and uses the addElement() method to add the strings, "one", "two", and "three" to the vector. It then uses the insertElementAt() method to insert the strings "zero", "oops", and "four" at locations 0, 3, and 5 within the vector. The size() method is used to retrieve the vector size for display to the console window.
The elements() method of the Vector class is used to retrieve an enumeration of the elements that were added to the vector. A while loop is then used to cycle through and print the elements contained in the enumeration. The hasMoreElements() method is used to determine whether the enumeration contains more elements. If it does, the nextElement() method is used to retrieve the object for printing.
The removeElement() of the Vector class is used to remove the vector element containing the string "oops". The new size of the vector is displayed and the elements of the vector are redisplayed. The for loop indexes each element in the vector using the elementAt() method.
The output of the VectorApp program is as follows:
Size: 6
zero one two oops three four
Size: 5
zero one two three four
The Stack class provides the capability to create and use stacks within your Java programs. Stacks are storage objects that store information by pushing it onto a stack and remove and retrieve information by popping it off the stack. Stacks implement a last-in-first-out storage capability: The last object pushed on a stack is the first object that can be retrieved from the stack. The Stack class extends the Vector class.
The Stack class provides a single default constructor, Stack(), that is used to create an empty stack.
Objects are placed on the stack using the push() method and retrieved from the stack using the pop() method. The search() method allows you to search through a stack to see if a particular object is contained on the stack. The peek() method returns the top element of the stack without popping it off. The empty() method is used to determine whether a stack is empty. The pop() and peek() methods both throw the EmptyStackException if the stack is empty. Use of the empty() method can help to avoid the generation of this exception.
The StackApp program demonstrates the operation of a stack. (See Listing 14.4.) It creates a Stack object and then uses the push() method to push the strings "one", "two", and "three" onto the stack. Because the stack operates in last-in-first-out fashion, the top of the stack is the string "three". This is verified by using the peek() method. The contents of the stack are then popped off and printed using a while loop. The empty() method is used to determine when the loop should terminate. The pop() method is used to pop objects off the top of the stack.
Listing 14.4. The source code of the StackApp program.import java.lang.System;
import java.util.Stack;
public class StackApp {
public static void main(String args[]){
Stack s = new Stack();
s.push("one");
s.push("two");
s.push("three");
System.out.println("Top of stack: "+s.peek());
while (!s.empty())
System.out.println(s.pop());
}
}
The output of the StackApp program is as follows:
Size: 6
zero one two oops three four
Size: 5
zero one two three four
The BitSet class is used to create objects that maintain a set of bits. The bits are maintained as a growable set. The capacity of the bit set is increased as needed. Bit sets are used to maintain a list of flags that indicate the state of each element of a set of conditions. Flags are boolean values that are used to represent the state of an object.
Two BitSet constructors are provided. One allows the initial capacity of a BitSet object to be specified. The other is a default constructor that initializes a BitSet to a default size.
The BitSet access methods provide and, or, and exclusive or logical operations on bit sets; enable specific bits to be set and cleared; and override general methods declared for the Object class.
The BitSetApp program demonstrates the operation of bit sets. (See Listing 14.5.)
Listing 14.5. The source code of the BitSetApp program.import java.lang.System;
import java.util.BitSet;
public class BitSetApp {
public static void main(String args[]){
int size = 8;
BitSet b1 = new BitSet(size);
for(int i=0;i<size;++i) b1.set(i);
BitSet b2 = (BitSet) b1.clone();
for(int i=0;i<size;i=i+2) b2.clear(i);
System.out.print("b1: ");
for(int i=0;i<size;++i) System.out.print(b1.get(i)+" ");
System.out.print("\nb2: ");
for(int i=0;i<size;++i) System.out.print(b2.get(i)+" ");
System.out.println();
System.out.println("b1: "+b1);
System.out.println("b2: "+b2);
b1.xor(b2);
System.out.println("b1 xor b2 = "+b1);
b1.and(b2);
System.out.println("b1 and b2 = "+b1);
b1.or(b2);
System.out.println("b1 or b2 = "+b1);
}
}
The program begins by creating a BitSet object, b1, of size 8. It executes a for statement to index through b1 and set each bit in the bit set. It then uses the clone() method to create an identical copy of b1 and assign it to b2. Another for statement is executed to clear every even-numbered bit in b2. The values of the b1 and b2 bit sets are then printed. This results in the display of two lists of boolean values. The bit sets are printed as objects, resulting in a set- oriented display. Only the bits with true boolean values are identified as members of the displayed bit sets.
The xor() method is used to compute the exclusive or of b1 and b2, updating b1 with the result. The new value of b1 is then displayed.
The and() method is used to calculate the logical and of b1 and b2, again, updating b1 with the result and displaying b1's new value.
Finally, the logical or of b1 and b2 is computed, using the or() method. The result is used to update b1, and b1's value is displayed.
The output of BitSetApp is as follows:
b1: true true true true true true true true
b2: false true false true false true false true
b1: {0, 1, 2, 3, 4, 5, 6, 7}
b2: {1, 3, 5, 7}
b1 xor b2 = {0, 2, 4, 6}
b1 and b2 = {}
b1 or b2 = {1, 3, 5, 7}
The Dictionary, Hashtable, and Properties classes are three generations of classes that implement the capability to provide key-based data storage and retrieval. The Dictionary class is the abstract superclass of Hashtable, which is, in turn, the superclass of Properties.
Dictionary provides the abstract functions used to store and retrieve objects by key-value associations. The class allows any object to be used as a key or value. This provides great flexibility in the design of key-based storage and retrieval classes. Hashtable and Properties are two examples of these classes.
The Dictionary class can be understood using its namesake abstraction. A real-world hardcopy dictionary maps words to their definition. The words can be considered the keys of the dictionary and the definitions as the values of the keys. Java dictionaries operate in the same fashion. One object is used as the key to access another object. This abstraction will become clearer as you investigate the Hashtable and Properties classes.
The Dictionary class defines several methods that are inherited by its subclasses. The elements() method is used to return an Enumeration object containing the values of the key-value pairs stored within the dictionary. The keys() method returns an enumeration of the dictionary keys. The get() method is used to retrieve an object from the dictionary based on its key. The put() method puts a Value object in the dictionary and indexes it using a Key object. The isEmpty() method determines whether a dictionary contains any elements, and the size() method identifies the dictionary's size in terms of the number of elements it contains. The remove() method deletes a key-value pair from the dictionary based on the object's key.
The Hashtable class implements a hash table data structure. A hash table indexes and stores objects in a dictionary using hash codes as the objects' keys. Hash codes are integer values that identify objects. They are computed in such a manner that different objects are very likely to have different hash values and therefore different dictionary keys.
The Object class implements the hashCode() method. This method allows the hash code of an arbitrary Java object to be calculated. All Java classes and objects inherit this method from Object. The hashCode() method is used to compute the hash code key for storing objects within a hash table. Object also implements the equals() method. This method is used to determine whether two objects with the same hash code are, in fact, equal.
The Java Hashtable class is very similar to the Dictionary class from which it is derived. Objects are added to a hash table as key-value pairs. The object used as the key is hashed, using its hashCode() method, and the hash code is used as the actual key for the value object. When an object is to be retrieved from a hash table, using a key, the key's hash code is computed and used to find the object.
The Hashtable class provides three constructors. The first constructor allows a hash table to be created with a specific initial capacity and load factor. The load factor is a float value between 0.0 and 1.0 that identifies the percentage of hash table usage that causes the hash table to be rehashed into a larger table. For example, suppose a hash table is created with a capacity of 100 entries and a 0.70 load factor. When the hash table is 70 percent full, a new, larger hash table will be created, and the current hash table entries will have their hash values recalculated for the larger table.
The second Hashtable constructor just specifies the table's initial capacity and ignores the load factor. The default hash table constructor does not specify either hash table parameter.
The access methods defined for the Hashtable class allow key-value pairs to be added to and removed from a hash table, search the hash table for a particular key or object value, create an enumeration of the table's keys and values, determine the size of the hash table, and recalculate the hash table, as needed. Many of these methods are inherited or overridden from the Dictionary class.
The HashApp program illustrates the operation and use of hash tables. (See Listing 14.6.)
Listing 14.6. The source code of the HashApp program.import java.lang.System;
import java.util.Hashtable;
import java.util.Enumeration;
public class HashApp {
public static void main(String args[]){
Hashtable h = new Hashtable();
h.put("height","6 feet");
h.put("weight","200 pounds");
h.put("eye color","blue");
h.put("hair color","brown");
System.out.println("h: "+h);
Enumeration enum = h.keys();
System.out.print("keys: ");
while (enum.hasMoreElements()) System.out.print(enum.nextElement()+", ");
System.out.print("\nelements: ");
enum = h.elements();
while (enum.hasMoreElements()) System.out.print(enum.nextElement()+", ");
System.out.println();
System.out.println("height: "+h.get("height"));
System.out.println("weight: "+h.get("weight"));
System.out.println("eyes: "+h.get("eye color"));
System.out.println("hair: "+h.get("hair color"));
h.remove("weight");
System.out.println("h: "+h);
}
}
The program begins by creating a Hashtable object using the default constructor. It then adds four key-value pairs to the hash table using the put() method. The hash table is then printed using the default print method for objects of class Hashtable.
The keys() method is used to create an enumeration of the hash table's keys. These keys are then printed one at a time by indexing through the enumeration object.
The elements() method is used to create an enumeration of the hash table's values. This enumeration is printed in the same way as the key enumeration.
The values of the hash table are again displayed by using the get() method to get the values corresponding to specific key values.
Finally, the remove() method is used to remove the key-value pair associated with the weight key and the hash table is reprinted using the default print convention.
The program output is as follows:
h: {height=6 feet, weight=200 pounds, eye color=blue, hair color=brown}
keys: height, weight, eye color, hair color,
elements: 6 feet, 200 pounds, blue, brown,
height: 6 feet
weight: 200 pounds
eyes: blue
hair: brown
h: {height=6 feet, eye color=blue, hair color=brown}
The Properties class is a subclass of Hashtable that can be read from or written to a stream. It also provides the capability to specify a set of default values to be used if a specified key is not found in the table. The default values themselves are specified as an object of class Properties. This allows an object of class Properties to have a default Properties object, which in turn has its own default properties, and so on.
Properties supports two constructors: a default constructor with no parameters and a constructor that accepts the default properties to be associated with the Properties object being constructed.
The Properties class declares several new access methods. The getProperty() method allows a property to be retrieved using a String object as a key. A second overloaded getProperty() method allows a value string to be used as the default in case the key is not contained in the Properties object.
The load() and save() methods are used to load a Properties object from an input stream and save it to an output stream. The save() method allows an optional header comment to be saved at the beginning of the saved object's position in the output stream.
The propertyNames() method provides an enumeration of all the property keys, and the list() method provides a convenient way to print a Properties object on a PrintStream object.
The PropApp program illustrates the use of the Properties class by saving a subset of the system properties to a byte array stream and then loading the properties back in from the byte array stream. (See Listing 14.7.)
Listing 14.7. The source code of the PropApp program.import java.lang.System;
import java.util.Properties;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class PropApp {
public static void main(String args[]) throws IOException {
Properties sysProp = System.getProperties();
sysProp.list(System.out);
sysProp.remove("java.home");
sysProp.remove("file.separator");
sysProp.remove("line.separator");
sysProp.remove("java.vendor");
sysProp.remove("user.name");
sysProp.remove("java.vendor.url");
sysProp.remove("user.dir");
sysProp.remove("java.class.path");
sysProp.remove("java.class.version");
sysProp.remove("path.separator");
sysProp.remove("user.home");
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
sysProp.save(outStream,"sysProp");
System.out.println("\noutputStream:\n"+outStream);
ByteArrayInputStream inStream;
inStream = new ByteArrayInputStream(outStream.toByteArray());
sysProp.load(inStream);
sysProp.list(System.out);
}
}
The program begins by using the getProperties() method of the System class to retrieve the system properties and assign them to the sysProp variable. The system properties are then listed on the console window using the list() method.
Eleven of the properties contained by sysProp are removed using the remove() method. They were removed to cut down on the amount of program output and to illustrate the use of the remove() method.
An object of class ByteArrayOutputStream is created and assigned to the outStream variable. The sysProp Properties object is then saved to the output stream using the save() method.
The outStream is converted to a byte array using the toByteArray() method of the ByteArrayOutputStream class. The byte array is provided as an argument to a ByteArrayInputStream constructor, which uses it to create an object of its class and assign it to the inStream variable. The saved properties are reloaded from inStream and reassigned to the sysProp variable. The contents of the sysProp variable are then relisted to show that they were correctly loaded.
The program output is as follows:
-- listing properties --
java.home=C:\JAVA\BIN\..
awt.toolkit=sun.awt.win32.MToolkit
java.version=1.0
file.separator=\
line.separator=
java.vendor=Sun Microsystems Inc.
user.name=jaworskij
os.arch=x86
os.name=Windows 95
java.vendor.url=http://www.sun.com/
user.dir=c:\java\jdg\ch14
java.class.path=.;c:\java;c:\java\lib;;C:\JAVA\BIN\.....
java.class.version=45.3
os.version=4.0
path.separator=;
user.home=\home\jamie
outputStream:
#sysProp
#Fri Mar 01 09:59:00 1996
awt.toolkit=sun.awt.win32.MToolkit
java.version=1.0
os.arch=x86
os.name=Windows 95
os.version=4.0
-- listing properties --
awt.toolkit=sun.awt.win32.MToolkit
java.version=1.0
os.arch=x86
os.name=Windows 95
os.version=4.0
The StringTokenizer class is used to create a parser for String objects. It parses strings according to a set of delimiter characters. It implements the Enumeration interface in order to provide access to the tokens contained within a string. The StringTokenizer class is similar to the StreamTokenizer class covered in Chapter 13, "Stream-Based Input/Output and the java.io Package."
StringTokenizer provides three constructors. All three have the input string as a parameter. The first constructor includes two other parameters: a set of delimiters to be using in the string parsing and a boolean value used to specify whether the delimiter characters should be returned as tokens. The second constructor accepts the delimiter string but not the return token's toggle. The last constructor uses the default delimiter set consisting of the space, tab, newline, and carriage-return characters.
The access methods provided by StringTokenizer include the Enumeration methods, hasMoreElements() and nextElement(), hasMoreTokens() and nextToken(), and countTokens(). The countTokens() method returns the number of tokens in the string being parsed.
The TokenApp program prompts the user to enter a line of keyboard input and then parses the line, identifying the number and value of the tokens that it found. (See Listing 14.8.)
Listing 14.8. The source code of the TokenApp program.import java.lang.System;
import java.util.StringTokenizer;
import java.io.DataInputStream;
import java.io.IOException;
public class TokenApp {
public static void main(String args[]) throws IOException {
DataInputStream keyboardInput = new DataInputStream(System.in);
int numTokens;
do {
System.out.print("=> ");
System.out.flush();
StringTokenizer st = new StringTokenizer(keyboardInput.readLine());
numTokens = st.countTokens();
System.out.println(numTokens+" tokens");
while (st.hasMoreTokens())
System.out.println(" "+st.nextToken());
} while(numTokens!=0);
}
}
The program begins by creating a DataInputStream object using the System.in stream as an argument to its constructor. A do loop is used to read a line of input from the user, construct a StringTokenizer object on the input line, display the number of tokens in the line, and display each token as parsed using the standard delimiter set. The loop continues until a line with no tokens is entered.
The program's output is as follows:
=> this is a test
4 tokens
this
is
a
test
=> 1 2 3 4.5 6
5 tokens
1
2
3
4.5
6
=> @ # $ % ^
5 tokens
@
#
$
%
^
=>
0 tokens
The Observer interface and Observable class are used to implement an abstract system by which observable objects can be observed by objects that implement the Observer interface. Observable objects are objects that subclass the abstract Observable class. These objects maintain a list of observers. When an observable object is updated, it invokes the update() method of its observers to notify the observers that it has changed state.
The update() method is the only method that is specified in the Observer interface. Theupdate() method is used to notify an observer that an observable has changed. The method takes the observable object and a second notification message Object as its parameters.
The Observable class is an abstract class that must be subclassed by observable objects. It provides several methods for adding, deleting, and notifying observers and for manipulating change status. These methods are described in the class's API page.
In this chapter you have learned how to work with all the useful utility classes contained in the java.util package. You have learned to use the Date class to manipulate Date objects and the Random number class to generate random numbers, and how to work with a range of data structures, including dictionaries, stacks, hash tables, vectors, and bit sets. In Chapter 15, "Window Programming with the java.awt Package," you'll preview the Java Abstract Windows Toolkit (AWT) and learn what window components are available to develop window-based programs using Java.