- Special Edition Using Java, 2nd Edition -

Chapter 30

java.util


by Mark Wutka

The java.util package provides several useful classes that give important functionality to the Java runtime environment. These classes provide much of the code that you frequently end up writing yourself when you write in C++. The creators of Java realized that one of the things people really like about Smalltalk is the abundance of useful utility classes.

The java.util package focuses mostly on container objects, that is, objects that contain or hold other objects. In addition to the containers, the package also adds a handy utility class for breaking a string up into words (tokens), expanded support for random numbers and dates, and a carryover from Smalltalk called observables.

The Vector Class

Java arrays are very powerful, but they don’t always fit your needs. Sometimes you want to put items in an array, but you don’t know how many items you will be getting. One way to solve this is to create an array larger than you think you’ll need. This was the typical approach in the days of C programming. The Vector class gives you an alternative to this, however. A vector is similar to an array in that it holds multiple objects, and you retrieve the objects using an index value. The big difference between arrays and vectors is that vectors automatically grow when they run out of room. They also provide extra methods for adding and removing elements that you would normally have to do manually in an array, such as inserting an element between two others.

Creating a Vector

When you create a vector, you can specify how big it should be initially, and how fast it should grow. You can also just set the vector’s initial size and let it figure out how fast to grow, or you can let the vector decide everything for itself. The Vector class has three constructors.

public Vector()

creates an empty vector.

public Vector(int initialCapacity)

creates a vector with space for initialCapacity elements.

public Vector(int initialCapacity, int capacityIncrement)

creates a vector with space for initialCapacity elements. Whenever the vector needs to grow, it grows by capacityIncrement elements.

public Vector()

creates an empty vector.

public Vector(int initialCapacity)

creates a vector with space for initialCapacity elements.

public Vector(int initialCapacity, int capacityIncrement)

creates a vector with space for initialCapacity elements. Whenever the vector needs to grow, it grows by capacityIncrement elements.

If you have some idea of the typical number of elements you will be adding, you should go ahead and set up the vector with space for that many elements. If you don’t use all the space, that’s okay, you just don’t want the vector to have to allocate more space over and over.

If you do not specify a capacity increment, the vector doubles its capacity when it grows. If you have a large vector, this may not be the desired behavior. When you are adding a large number of elements to a vector, you should set a specific capacity increment.
 

Adding Objects to a Vector

There are two ways to add new objects to a vector. You can add an object as the last element in the vector, or you can insert an object in between two existing objects. The addElement method adds an object as the last element:

public final synchronized void addElement(Object newElement)

The insertElementAt adds a new object at a specific position. The index parameter indicates where in the vector the new object should be placed:

public final synchronized void insertElementAt(Object newElement, int index)
throws ArrayIndexOutOfBoundsException

If you try to insert the new element at a position that does not exist yet—for instance, if you try to insert at position 9 and there are only five elements in the vector—you get an ArrayIndexOutOfBoundsException.

You can change the object at a specific position in the vector with the setElementAt method:

public final synchronized void setElementAt(Object ob, int index)
throws ArrayOutOfBoundsException

This method works almost exactly like the insertElementAt method, except that the other elements in the vector are not shifted over to make room for a new object. In other words, the new object replaces the old one in the vector.

Accessing Objects in a Vector

Unfortunately, accessing objects in a vector is not quite as simple as accessing array elements. Instead of giving an index surrounded by brackets ([]), you use the elementAt method to access vector elements. The vector equivalent of someArray[4] is someVector.elementAt(4). The format of the elementAt method is:

public final synchronized Object elementAt(int index)
throws ArrayIndexOutOfBoundsException

You can also access the first and last elements in a vector with the firstElement and lastElement methods:

public final synchronized Object firstElement()
throws NoSuchElementException
public final synchronized Object lastElement()
throws NoSuchElementException

If there are no objects stored in the vector, these methods both throw a NoSuchElementException.

You can test to see if a vector has no elements using the isEmpty method:

public final boolean isEmpty()

Many times you want to use a vector to build up a container of objects, but then convert the vector over to a Java array for speed purposes. You usually only do this after you have all the objects you need. For instance, if you are reading objects from a file that can contain any number of objects, you store the objects in a vector. When you have finished reading the file, you create an array of objects and copy them out of the vector. The size method tells you how many objects are stored in the vector:

public final int size()

After you know the size of the vector, you can create an array of objects using this size. The Vector class provides a handy method for copying all the objects in a vector into an array of objects:

public final synchronized void copyInto(Object[] obArray)

If you try to copy more objects into the array than it can hold, you get an ArrayIndexOutOfBounds exception. The following code fragment creates an object array and copies the contents of a vector called myVector into it:

Object obArray[] = new Object[myVector.size()]; // Create object array
myVector.copyInto(obArray); // Copy the vector into the array

The Enumeration Interface

If you want to cycle through all the elements in a vector, you can use the elements method to get an Enumeration object for the vector. An Enumeration is responsible for accessing elements in a data structure sequentially. It contains two methods.

public abstract boolean hasMoreElements()

returns true while there are still more elements to access. When there are no more elements left, this method returns false.

public abstract Object nextElement()
throws NoSuchElementException

returns a reference to the next element in the data structure. If there are no more elements to access and you call this method again, you get a NoSuchElementException.

In the case of the Vector class, the elements method returns an Enumeration interface for the vector:

public final synchronized Enumeration elements()

The following code fragment uses an Enumeration interface to examine every object in a vector:

Enumeration vectEnum = myVector.elements(); // get the vector’s enumeration
while (vectEnum.hasMoreElements()) // while there’s something to get…
{
Object nextOb = vectEnum.nextElement(); // get the next object
// do whatever you want with the next object
}

This loop works the same for every data structure that can return an Enumeration object. A data structure typically has an elements method, or something similar, that returns the enumeration. After that, the kind of data structure doesn’t matter—they all look the same through the Enumeration interface.

Searching for Objects in a Vector

You can always search for objects in a vector manually, by using an enumeration and doing an element-by-element comparison, but you will save a lot of time by using the built-in search functions.

If you just need to know if an object is present in a vector, use the contains method. For example:

public final boolean contains(Object ob)

returns true if ob occurs at least once in the vector, or false if not.

You can also find out an object’s position in a vector with the indexOf and lastIndexOf methods. For example:

public final int indexOf(Object ob)

returns the position in the vector where the first occurrence of ob is found, or -1 if ob is not present in the vector.

public final synchronized int indexOf(Object ob, int startIndex)
throws ArrayIndexOutOfBoundsException

returns the position in the vector where the first occurrence of ob is found, starting at position startIndex. If ob is not in the vector, it returns -1. If startIndex is less than 0, or greater than or equal to the vector’s length, you get an ArrayOutOfBoundsException.

public final int lastIndexOf(Object ob)

returns the position in the vector where the last occurrence of ob is found, or -1 if ob is not present in the vector.

public final synchronized int lastIndexOf(Object ob, int startIndex)
throws ArrayOutOfBoundsException

returns the position in the vector where the last occurrence of ob is found, starting at position startIndex. If ob is not in the vector, it returns -1. If startIndex is less than 0, or greater than or equal to the vector’s length, you get an ArrayOutOfBoundsException.

Removing Objects from a Vector

You have three options when it comes to removing objects from a vector. You can remove all the objects, remove a specific object, or remove the object at a specific position. The removeAllElements method removes all the objects from a vector:

public final synchronized void removeAllElements()

The removeElement method removes a specific object from a vector:

public final synchronized boolean removeElement(Object ob)

If the object occurs more than once, only the first occurrence is removed. The method returns true if an object was actually removed, or false if the object was not found in the vector.

The removeElementAt method removes the object at a specific position, and moves the other objects over to fill in the gap created by the removed object:

public final synchronized void removeElementAt(int index)
throws ArrayIndexOutOfBoundsException

If you try to remove an object from a position that does not exist, you get an ArrayIndexOutOfBoundsException.

Changing the Size of a Vector

A vector has two notions of size—the number of elements currently stored in the vector and the maximum capacity of the vector. The capacity method tells you how many objects the vector can hold before having to grow:

public final int capacity()

You can increase the capacity of a vector using the ensureCapacity method. For example:

public final synchronized void ensureCapacity(int minimumCapacity)

tells the vector that it should be able to store at least minimumCapacity elements. If the vector’s current capacity is less than minimumCapacity, it allocates more space. The vector does not shrink the current capacity if the capacity is already higher than minimumCapacity.

If you want to reduce a vector’s capacity, use the trimToSize method:

public final synchronized void trimToSize()

This method reduces the capacity of a vector down to the number of elements it is currently storing.

The size method tells you how many elements are stored in a vector:

public final int size()

You can use the setSize method to change the current number of elements:

public synchronized final void setSize(int newSize)

If the new size is less than the old size, the elements at the end of the vector are lost. If the new size is higher than the old size, the new elements are set to null. Calling setSize(0) is the same as calling removeAllElements().

The Dictionary Class

The Dictionary class is an abstract class that provides methods for associating one object with another. Dictionaries are often used to associate a name with an object and retrieve the object based on that name. In a dictionary, the name object is called a key, and it can be any kind of object. The object associated with the key is called the value. A key can be associated with only one value, but a value can have more than one key.

Storing Objects in a Dictionary

To store an object in a dictionary with a specific key, use the put method:

public abstract Object put(Object key, Object value)
throws NullPointerException

The object returned by the put method is the object that was previously associated with the key. If there was no previous association, the method returns null. You cannot have a null key or a null value. If you pass null for either of these parameters you get a NullPointerException.

Retrieving Objects from a Dictionary

The get method finds the object in the dictionary associated with a particular key:

public abstract Object get(Object key)

The get method returns null if there is no value associated with that key.

Removing Objects from a Dictionary

To remove a key-value pair from a dictionary, call the remove method with the key. For example:

public abstract Object remove(Object key)

returns the object that is associated with the key, or null if there is no value associated with that key.

The Dictionary class also provides some utility methods that give you information about the dictionary. The isEmpty method returns true if there are no objects stored in the dictionary:

public abstract boolean isEmpty()

The size method tells you how many key-value pairs are currently stored in the dictionary:

public abstract int size()

The keys method returns an Enumeration object that allows you to examine all the keys in the dictionary, while the elements method returns an Enumeration for all the values in the dictionary:

public abstract Enumeration keys()

public abstract Enumeration elements()

A Simple Dictionary Implementation

Because there are so many ways of organizing dictionaries, you may find yourself writing a dictionary class. The following example dictionary uses a Vector object to store the keys and values. It is a very inefficient way to store them, but it makes a good, quick example. The Hashtable class, discussed in the next section, is much more efficient than this class. Listing 30.1 shows the SimpleDictionary class, while listing 30.2 shows the SimpleDictEnum class, which implements an Enumeration interface for the dictionary.

Listing 30.1 Source Code for SimpleDictionary.java.

import java.util.*;
// This class uses a Vector to implement a simple
// dictionary lookup. The lookup is performed by
// comparing every element in the vector until a
// match is found. Each element in the vector is
// a 2-element array containing the key and its
// associated value.
// This is not the fastest way to do a lookup, it
// just serves as an example of how to implement
// your own dictionary scheme.
public class SimpleDictionary extends Dictionary
{
protected Vector lookupVector; // holds the key-value pairs
public SimpleDictionary()
{
lookupVector = new Vector();
}
public synchronized Object put(Object key,
Object value)
{
if (key == null) {
throw new NullPointerException("Key is null");
}
if (value == null) {
throw new NullPointerException("Value is null");
}
// Go through the vector looking for the key
Enumeration e = lookupVector.elements();
while (e.hasMoreElements()) {
// key-value pairs are stored as an array of Objects, get the next pair
Object keyValuePair[] = (Object[]) e.nextElement();
// Compare the keys, must use equals, not ==. Otherwise, if you put
// an object with a string of "Foo" and try to get it with a string of "Foo"
// the == might say they weren't the same key.
if (keyValuePair[0].equals(key)) {
// If we got a match, replace the old value with the new one and
// return the old value
Object oldObject = keyValuePair[1];
keyValuePair[1] = value;
return oldObject;
}
}
// If the key wasn't already in the dictionary, create a new key-value
// pair and store it in the vector
Object keyValuePair[] = new Object[2];
keyValuePair[0] = key;
keyValuePair[1] = value;
lookupVector.addElement(keyValuePair);
return null;
}
// get looks up an object by its key
public synchronized Object get(Object key)
{
int size = lookupVector.size();
for (int i=0; i < size; i++) {
// We loop through with a for loop instead of an Enumeration, it's about
// 4 times as fast this way
Object keyValuePair[] =
(Object[]) lookupVector.elementAt(i);
// Compare the keys, if we find a match, return the value
if (keyValuePair[0].equals(key)) {
return keyValuePair[1];
}
}
return null; // no match found
}
// remove will remove an object with a specific key
public synchronized Object remove(Object key)
{
int size = lookupVector.size();
for (int i=0; i < size; i++) {
// Get the next key-value pair
Object keyValuePair[] =
(Object[]) lookupVector.elementAt(i);
// Compare the keys
if (keyValuePair[0].equals(key)) {
// If we have a match, remove this element from the vector and return
// the object that was stored with this key
lookupVector.removeElementAt(i);
return keyValuePair[1];
}
}
return null; // key not found
}
// The dictionary is empty if the vector is empty
public boolean isEmpty()
{
return lookupVector.size() == 0;
}
// The number of elements in the dictionary is the same as the
// number of elements in the lookup vector
public int size()
{
return lookupVector.size();
}
// The enumeration returned here is wrapped around the lookup
// vector enumeration. It works for both keys and elements. The
// second parameter in the constructor determines if it returns
// the key (0) or the value (1).
public Enumeration keys()
{
return new SimpleDictEnum(lookupVector.elements(), 0);
}
public Enumeration elements()
{
return new SimpleDictEnum(lookupVector.elements(), 1);
}
}

Listing 30.2 Source Code for SimpleDictEnum.java.

import java.util.*;
// This class does the enumeration for the SimpleDictionary
// It takes an enumeration for the SimpleDictionary's
// lookup vector and returns the key portion if
// whichElement == 0, or the value portion if whichElement == 1
public class SimpleDictEnum extends Object implements Enumeration
{
protected Enumeration vectEnum;
protected int whichElement;
public SimpleDictEnum(Enumeration vectEnum,
int whichElement)
{
this.vectEnum = vectEnum;
this.whichElement = whichElement;
}
// The dictionary has more elements if the lookup vector has
// more elements, since they have a one-to-one relationship
public boolean hasMoreElements()
{
return vectEnum.hasMoreElements();
}
public Object nextElement()
{
// Get the key-value pair
Object[] keyValuePair = (Object[]) vectEnum.nextElement();
// Return either the key or the value depending on whichElement
return keyValuePair[whichElement];
}
}

The Hashtable Class

The Hashtable class is an implementation of the Dictionary class that uses the hash codes of the key objects to perform the lookup. It groups keys into "buckets" based on their hash code. When it goes to find a key, it queries the key’s hash code, uses the hash code to get the correct bucket and then searches the bucket for the correct key. Usually, the number of keys in the bucket is very small compared to the total number of keys in the hash table, so the hash table performs only a fraction of the comparisons performed in the SimpleDictionary example in the last section.

The hash table has a capacity, which tells how many buckets it uses, and a load factor, which is the ratio of the number of elements in the table to the number of buckets. When you create a hash table, you can specify a load factor threshold value. When the current load factor exceeds this threshold, the table grows—that is, it doubles the number of buckets and then reorganizes the table. The default load factor threshold is 0.75, which means that when the number of elements stored in the table is 75 percent of the number of buckets, the number of buckets is doubled. You can specify any load factor threshold greater than 0 and less than or equal to 1. A smaller threshold means a faster lookup, since there will be very few keys per bucket (maybe no more than one), but the table will have far more buckets than elements, so there is some wasted space. A larger threshold means the possibility of slower lookups, but the number of buckets is closer to the number of elements.

The Hashtable class has three constructors.

public Hashtable()

creates a new hash table with a default capacity of 101, and a default load factor threshold of 0.75.

public Hashtable(int initialCapacity)

creates a new hash table with the specified initial capacity, and a default load factor threshold of 0.75.

public Hashtable(int initialCapacity, float loadFactorThreshold)
throws IllegalArgumentException

creates a new hash table with the specified initial capacity and threshold. If the initial capacity is 0 or less, or if the threshold is 0 or less, or greater than 1, you get an IllegalArgumentException.

In addition to supporting all the methods from the Dictionary class, the Hashtable adds a few more methods.

public synchronized void clear()

removes all the elements from the hash table. This is similar to the removeAllElements method in the Vector class.

public synchronized boolean contains(Object value)
throws NullPointerException

returns true if value is stored as a value in the hash table. If value is null, it throws a NullPointerException.

public synchronized boolean containsKey(Object key)

returns true if key is stored as a key in the hash table.

When a hash table grows in size, it has to rearrange all the objects in the table over the new set of buckets. In other words, if there were 512 buckets, and the table grew to 1,024 buckets, you need to redistribute the objects over the full 1,024 buckets. An object’s bucket is determined by a combination of both the hash code and the number of buckets. If you were to change the number of buckets, but not rearrange the objects, the hash table might not be able to locate an existing object, because its bucket was determined based on a smaller size. The rehash method, (which is automatically called when the table grows) recomputes the location of each object in the table.

The Properties Class

The Properties class is a special kind of dictionary that uses strings for both keys and values. It is used by the System class to store system properties, but you can use it to create your own set of properties. The Properties class is actually a just a hash table that specializes in storing strings.

You can create a new properties object with the empty constructor:

public Properties()

You can also create a properties object with a set of default properties. When the properties object cannot find a property in its own table, it searches the default properties table. If you change a property in your own properties object, it does not change the property in the default properties object. This means that multiple properties objects can safely share the same default properties object. To create a properties object with a default set of properties, just pass the default properties object to the constructor:

public Properties(Properties defaultProps)

Setting Properties

You set properties using the same put method that all dictionaries use:

public Object put(Object key, Object value)
throws NullPointerException

Querying Properties

The getProperty method returns the string corresponding to a property name, or null if the property is not set:

public String getProperty(String key)

If you specify a default properties object, that object is also checked before null is returned. You can also call getProperty and specify a default value to be returned if the property is not set:

public String getProperty(String key, String defaultValue)

In this version of the getProperty method, the default properties object is completely ignored. The value returned is either the property corresponding to the key, or, if the property is not set, defaultValue.

Because the Properties class uses the put method from the Dictionary class, you can store objects other than strings in a Properties object. However, if you store a property that is not a String or a subclass of String, you get a ClassCastException when you try to retrieve it with the getProperty method. It is a good practice to use the toString method in an object to insure that you are storing a string representation and not a non-string object.
 

You can get an Enumeration object for all the property names in a Properties object, including the default properties, with the propertyNames method:

public Enumeration propertyNames()

Saving and Retrieving Properties

Because the Properties class is so useful for storing things like a user's preferences, you need a way to save the properties to a file and read them back the next time your program starts. You can use the load and save methods for this.

public synchronized void save(OutputStream out, String header)

saves the properties on the output stream out. The header string is written to the stream before the contents of the properties object.

public synchronized void load(InputStream in)
throws IOException

reads properties from the input stream. It treats the # and ! characters as comment characters and ignores anything after them up to the end of the line, similar to the // comment characters in Java.

Listing 30.3 shows a sample file that was written by the save method.

Listing 30.3 File written by the save method.

#Example Properties
#Mon Jun 17 19:57:39 1996
foo=bar
favoriteStooge=curly
helloMessage=hello world!

The list method is similar to the same method, but it presents the properties in a more readable form. It displays the contents of a properties table on a print stream in a nice, friendly format, which is very handy for debugging. The format of the list method is:

public void list(PrintStream out)

The Stack Class

A stack is a very handy data structure that adds items in a last-in, first-out manner. In other words, when you ask a stack to give you the next item, it hands back the most recently added item. Think of the stack as a stack of cafeteria trays. The tray on the top of the stack is the last tray you put on the stack. Every time you add another tray it becomes the new top of the stack.

The Stack class is implemented as a subclass of Vector, which means that all the vector methods are available to you in addition to the stack-specific ones. You create a stack with the empty constructor:

public Stack()

To add an item to the top of the stack, you push it onto the stack:

public Object push(Object newItem)

The object returned by the push method is the same as the newItem object. The pop method removes the top item from the stack:

public Object pop() throws EmptyStackException

If you try to pop an item off an empty stack you get an EmptyStackException. You can find out which item is on top of the stack without removing it by using the peek method:

public Object peek() throws EmptyStackException

The empty method returns true if there are no items on the stack:

public boolean empty()

Sometimes you may want to find out where an object is in relation to the top of the stack. Because you don’t know exactly how the stack stores items, the indexOf and lastIndexOf methods from the Vector class may not do you any good. The search method, however, tells you how far an object is from the top of the stack:

public int search(Object ob)

If the object is not on the stack at all, search returns -1.

The fragment of code in listing 30.4 creates an array of strings, then uses a stack to reverse the order of the words by pushing them all on the stack and popping them back off.

Listing 30.4 Example usage of a stack.

String myArray[] = { "Please", "Reverse", "These", "Words" };
Stack myStack = new Stack();
// Push all the elements in the array onto the stack
for (int i=0; i < myArray.length; i++) {
myStack.push(myArray[i]);
}
// Pop the elements off the stack and put them in the
// array starting at the beginning
for (int i=0; i < myArray.length; i++) {
myArray[i] = (String) myStack.pop();
}
// At this point, the words in myArray will be in
// the order: Words, These, Reverse, Please

The Date Class

The Date class represents a specific date and time. It is centered around the Epoch, which is midnight GMT on January 1, 1970. Although there is some support in the Date class for referencing dates as early as 1900, none of the date methods function properly on dates occurring before the Epoch.

The empty constructor for the Date class creates a Date object from the current time:

public Date()

You can also create a Date object using the number of milliseconds from the Epoch, the same kind of value returned by System.currentTimeMillis():

public Date(long millis)

You can also get the milliseconds since the Epoch by using the static UTC method in the Date class (UTC stands for Universal Time Coordinates):

public static long UTC(int year, int month, int date,
int hours, int minutes, int seconds)

The following Date constructors allow you to create a Date object by giving a specific year, month, day, and so on:

public Date(int year, int month, int date)
public Date(int year, int month, int date, int hours, int minutes)
public Date(int year, int month, int date, int hours, int minutes, int seconds)

There are several important things to note when creating dates this way:

The Date class also has the capability to create a new Date object from a string representation of a date:

public Date(String s)

The following statements all create Date objects for January 12, 1992 (the birthday of the HAL 9000 computer):

Date d = new Date("January 12, 1992");
Date d = new Date(92, 0, 12);
Date d = new Date(695174400000l); // milliseconds since the epoch
Date d = new Date(Date.UTC(92, 0, 12, 0, 0, 0));

Whenever you create a date using specific year, month, date, hour, minute, and second values, or when you print out the value of a Date object, it uses the local time zone. The UTC method and the number of milliseconds since the Epoch are always in GMT (Greenwich Mean Time).
 

Comparing Dates

As is true with all subclasses of Object, you can compare two dates with the equals method. The Date class also provides methods for determining whether one date comes before or after another. The after method in a Date object returns true if the date comes after the date passed to the method:

public boolean after(Date when)

The before method tells whether a Date object occurs before a specific date:

public boolean before(Date when)

Supposed you defined date1 and date2 as:

Date date1 = new Date(76, 6, 4); // July 4, 1976
Date date2 = new Date(92, 0, 12); // January 12, 1992

For these two dates, date1.before(date2) is true, and date1.after(date2) is false.

Converting Dates to Strings

You can always use the toString method to convert a date to a string. It converts the date to a string representation using your local time zone. The toLocaleString method also converts a date to a string representation using the local time zone, but the format of the string is slightly different:

public String toLocaleString()

The toGMTString method converts a date to a string using GMT as the time zone:

public String toGMTString()

The following example shows the formats of the different string conversions. The original time was defined as midnight GMT, January 12, 1992. The local time zone is Eastern Standard, or 5 hours behind GMT.

Sat Jan 11 19:00:00 1992 // toString
01/11/92 19:00:00 // toLocaleString
12 Jan 1992 00:00:00 GMT // toGMTString

Changing Date Attributes

You can query and change almost all the parts of a date. The only two things that you can query but not change are the time zone offset and the day of the week a date occurs on. The time zone offset is the number of minutes between the local time zone and GMT. The number is positive if your time zone is behind GMT—that is, if midnight GMT occurs before midnight in your time zone. The format of getTimezoneOffset is:

public int getTimezoneOffset()

The getDay method returns a number between 0 and 6, where 0 is Sunday:

public int getDay()

Remember that the day is computed using local time.

If you prefer to deal with dates in terms of the raw number of milliseconds since the Epoch, you can use the getTime and setTime methods to modify the date:

public long getTime()
public void setTime(long time)

You can also manipulate the individual components of the dates using these methods:

public int getYear()
public int getMonth()
public int getDate()
public int getHours()
public int getMinutes()
public int getSeconds()
public void setYear(int year)
public void setMonth(int month)
public void setDate(int date)
public void setHours(int hours)
public void setMinutes(int minutes)
public void setSeconds(int seconds)

The BitSet Class

The BitSet class provides a convenient way to perform bitwise operations on a large number of bits, and to manipulate individual bits. The BitSet automatically grows to handle more bits. You can create an empty bit set with the empty constructor:

public BitSet()

If you have some idea how many bits you will need, you should create the BitSet with a specific size:

public BitSet(int numberOfBits)

Bits are like light switches, they can be either on or off. If a bit is set, it is considered on, while it is considered off if it is cleared. Bits are frequently associated with boolean values, since each has only two possible values. A bit that is set is considered to be true, while a bit that is cleared is considered to be false.

You use the set and clear methods to set and clear individual bits in a bit set:

public void set(int whichBit)
public void clear(int whichBit)

If you create a bit set of 200 bits, and you try to set bit number 438, the bit set automatically grows to contain at least 438 bits. The new bits will all be cleared initially. The size method tells you how many bits are in the current bit set:

public int size()

You can test to see if a bit is set or cleared using the get method:

public boolean get(int whichBit)

The get method returns true if the specified bit is set, or false if it is cleared.

There are three operations you can perform between two bit sets. These operations manipulate the current bit set using bits from a second bit set. Corresponding bits are matched to perform the operation. In other words, bit 0 in the current bit set is compared to bit 0 in the second bit set. The bitwise operations are:

The format of these bitwise operations is:

public void or(Bitset bits)

public void and(Bitset bits)

public void xor(Bitset bits)

The StringTokenizer Class

The StringTokenizer class helps you parse a string by breaking it up into tokens. It recognizes tokens based on a set of delimiters. A token is considered a to be a string of characters that are not delimiters. For example, the phrase "I am a sentence" contains a number of tokens with spaces as delimiters. The tokens are I, am, a, and sentence. If you were using the colon character as a delimiter, the sentence would be one long token called I am a sentence, since there are no colons to separate the words. The StringTokenizer is not bound by the convention that words are separated by spaces. If you tell it that words are only separated by colons, it considers spaces to be part of a word.

You can even use a set of delimiters, meaning that many different characters can delimit tokens. For example, if you had the string "Hello. How are you? I am fine, I think," you would want to use a space, period, comma, and question mark as delimiters to break the sentence into tokens that are only words.

The string tokenizer doesn’t have a concept of words itself, it only understands delimiters. When you are parsing text, you usually use whitespace as a delimiter. Whitespace consists of spaces, tabs, newlines, and returns. If you do not specify a string of delimiters when you create a string tokenizer, it uses whitespace.

You create a string tokenizer by passing the string to be tokenized to the constructor:

public StringTokenizer(String str)

If you want something other than whitespace as a delimiter, you can also pass a string containing the delimiters you want to use:

public StringTokenizer(String str, String delimiters)

Sometimes you want to know what delimiter is used to separate two tokens. You can ask the string tokenizer to pass delimiters back as tokens by passing true for the returnTokens parameter in this constructor:

public StringTokenizer(String str, String delimiters, boolean returnTokens)

The nextToken method returns the next token in the string:

public String nextToken()
throws NoSuchElementException

If there are no more tokens, it throws a NoSuchElementException. You can use the hasMoreTokens method to determine if there are more tokens before you use nextToken:

public boolean hasMoreTokens()

You can also change the set of delimiters on-the-fly by passing a new set of delimiters to the nextToken method:

public String nextToken(String newDelimiters)

The new delimiters take effect before the next token is parsed, and stay in effect until they are changed again.

The countTokens method tells you how many tokens are in the string, assuming that the delimiter set doesn’t change:

public int countTokens()

You may have noticed that the nextToken and hasMoreTokens methods look similar to the nextElement and hasMoreElements methods in the Enumeration interface. They are so similar, in fact, that the StringTokenizer also implements an Enumeration interface that is implemented as:

public boolean hasMoreElements() {
return hasMoreTokens();
}
public Object nextElement() {
return nextToken();
}

The following code fragment prints out the words in a sentence using a string tokenizer:

String sentence = “This is a sentence”;
StringTokenizer tokenizer = new StringTokenizer(sentence);
while (tokenizer.hasMoreTokens())
{
System.out.println(tokenizer.nextToken());
}

The Random Class

The Random class provides a random number generator that is more flexible than the random number generator in the Math class. Actually, the random number generator in the Math class just uses one of the methods in the Random class. Since the methods in the Random class are not static, you must create an instance of Random before you generate numbers. The easiest way to do this is with the empty constructor:

public Random()

One of the handy features of the Random class is that it lets you set the random number seed that determines the pattern of random numbers. Although you cannot easily predict what numbers will be generated with a particular seed, you can duplicate a series of random numbers by using the same seed. In other words, if you create an instance of Random with the same seed value every time, you will get the same sequence of random numbers every time. This may not be good for writing games, and would be financially devastating for lotteries, but it is useful when writing simulations where you want to replay the same sequences over and over. The empty constructor uses System.currentTimeMillis to seed the random number generator. To create an instance of Random with a particular seed, just pass the seed value to the constructor:

public Random(long seed)

You can change the seed of the random number generator at any time using the setSeed method:

public synchronized void setSeed(long newSeed)

The Random class can generate random numbers in four different data types.

public int nextInt()

generates a 32-bit random number that can be any legal int value.

public long nextLong()

generates a 64-bit random number that can be any legal long value.

public float nextFloat()

generates a random float value between 0.0 and 1.0, though always less than 1.0.

public double nextDouble()

generates a random double value between 0.0 and 1.0, always less than 1.0. This is the method used by the Math.random method.

There is also a special variation of random number that has some interesting mathematical property. This variation is called nextGaussian.

public synchronized double nextGaussian()

returns a special random double value that can be any legal double value. The mean (average) of the values generated by this method is 0.0, and the standard deviation is 1.0. This means that the numbers generated by this method are usually close to zero, and that very large numbers are fairly rare.

The Observable Class

The Observable class allows an object to notify other objects when it changes. The concept of observables is borrowed from Smalltalk. In Smalltalk, an object may express interest in another object, meaning it would like to know when the other object changes.

When building user interfaces, you might have multiple ways to change a piece of data, and changing that data might cause several different parts of the display to update. For instance, suppose you want to create a scroll bar that changes an integer value, and, in turn, that integer value is displayed on some sort of graphical meter. You want the meter to update as the value is changed, but you don’t want the meter to know anything about the scroll bar. If you are wondering why the meter shouldn’t know about the scroll bar, what happens if you decide you don’t want a scroll bar but want the number entered from a text field instead? You shouldn’t have to change the meter every time you change the input source.

You would be better off creating an integer variable that is observable. It allows other objects to express interest in it. When this integer variable changes, it notifies those interested parties (called observers) that it has changed. In the case of the graphical meter, it would be informed that the value changed and would query the integer variable for the new value and then redraw itself. This allows the meter to display the value correctly no matter what you are using to change the value.

This concept is known as Model-View-Controller. A model is the non-visual part of an application. In the preceding example, the model is nothing more than a single integer variable. The view is anything that visually displays some part of the model. The graphical meter is an example of a view. The scroll bar could also be an example of a view since it updates its position whenever the integer value changes. A controller is any input source that modifies the view. The scroll bar in this case is also a controller (it can be both a view and a controller).

In Smalltalk, the mechanism for expressing interest in an object is built right in to the Object class. Unfortunately, for whatever reason, Sun separated out the observing mechanism into a separate class. This means extra work for you since you cannot just register interest in an Integer class, you must create your own subclass of Observable.

The most important methods to you in creating a subclass of Observable are setChanged and notifyObservers. The setChanged method marks the observable as having been changed, so that when you call notifyObservers the observers are notified:

protected synchronized void setChanged()

The setChanged method sets an internal changed flag that is used by the notifyObservers method. It is automatically cleared when notifyObservers is called, but you can clear it manually with the clearChanged method:

protected synchronized void clearChanged()

The notifyObservers method checks to see if the changed flag has been set, and if not, it does not send any notification:

public void notifyObservers()

The following code fragment sets the changed flag and notifies the observers of the change:

setChanged(); // Flag this observable as changed
notifyObservers(); // Tell observers about the change

The notifyObservers method can also be called with an argument:

public void notifyObservers(Object arg)

This argument can be used to pass additional information about the change—for instance, the new value. Calling notifyObservers with no argument is equivalent to calling it with an argument of null.

You can determine if an observable has changed by calling the hasChanged method:

public synchronized boolean hasChanged()

Observers can register interest in an Observable by calling the addObserver method:

public synchronized void addObserver(Observer obs)

Observers can deregister interest in an Observable by calling deleteObserver:

public synchronized void deleteObserver(Observer obs)

An observable can clear out its list of observers by calling the deleteObservers method:

public synchronized void deleteObservers()

The countObservers method returns the number of observers registered for an observable:

public synchronized int countObservers()

Listing 30.5 shows an example implementation of an ObservableInt class.

Listing 30.5 Source Code for ObservableInt.java.

import java.util.*;
// ObservableInt - an integer Observable
//
// This class implements the Observable mechanism for
// a simple int variable.
// You can set the value with setValue(int)
// and int getValue() returns the current value.
public class ObservableInt extends Observable
{
int value; // The value everyone wants to observe
public ObservableInt()
{
value = 0; // By default, let value be 0
}
public ObservableInt(int newValue)
{
value = newValue; // Allow value to be set when created
}
public synchronized void setValue(int newValue)
{
//
// Check to see that this call is REALLY changing the value
//
if (newValue != value)
{
value = newValue;
setChanged(); // Mark this class as "changed"
notifyObservers(); // Tell the observers about it
}
}
public synchronized int getValue()
{
return value;
}
}

The Observable class has a companion interface called Observer. Any class that wants to receive updates about a change in an observable needs to implement the Observer interface. The Observer interface consists of a single method called update that is called when an object changes. The format of update is:

public abstract void update(Observable obs, Object arg);

where obs is the Observable that has just changed, and arg is a value passed by the observable when it called notifyObservers. If notifyObservers is called with no arguments, arg is null.

Listing 30.6 shows an example of a Label class that implements the Observer interface so it can be informed of changes in an integer variable and update itself with the new value.

Listing 30.6 Source Code for IntLabel.java.

import java.awt.*;
import java.util.*;
//
// IntLabel - a Label that displays the value of
// an ObservableInt.
public class IntLabel extends Label implements Observer
{
private ObservableInt intValue; // The value we're observing
public IntLabel(ObservableInt theInt)
{
intValue = theInt;
// Tell intValue we're interested in it
intValue.addObserver(this);
// Initialize the label to the current value of intValue
setText(""+intValue.getValue());
}
// Update will be called whenever intValue is changed, so just update
// the label text.
public void update(Observable obs, Object arg)
{
setText(""+intValue.getValue());
}
}

Now that you have a model object defined in the form of the ObservableInt, and a view in the form of the IntLabel, you can create a controller—the IntScrollbar. Listing 30.7 shows the implementation of IntScrollbar.

Listing 30.7 Source code for IntScrollbar.java.

import java.awt.*;
import java.util.*;
//
// IntScrollbar - a Scrollbar that modifies an
// ObservableInt. This class functions as both a
// "view" of the observable, since the position of
// the scrollbar is changed as the observable's value
// is changed, and it is a "controller" since it also
// sets the value of the observable.
//
// IntScrollbar has the same constructors as Scrollbar,
// except that in each case, there is an additional
// parameter that is the ObservableInt.
// Note: On the constructor where you pass in the initial
// scrollbar position, the position is ignored.
public class IntScrollbar extends Scrollbar implements Observer
{
private ObservableInt intValue;
// The bulk of this class is implementing the various
// constructors that are available in the Scrollbar class.
public IntScrollbar(ObservableInt newValue)
{
super(); // Call the Scrollbar constructor
intValue = newValue;
intValue.addObserver(this); // Register interest
setValue(intValue.getValue()); // Change scrollbar position
}
public IntScrollbar(ObservableInt newValue, int orientation)
{
super(orientation); // Call the Scrollbar constructor
intValue = newValue;
intValue.addObserver(this); // Register interest
setValue(intValue.getValue()); // Change scrollbar position
}
public IntScrollbar(ObservableInt newValue, int orientation,
int value, int pageSize, int lowValue, int highValue)
{
super(orientation, value, pageSize, lowValue, highValue);
intValue = newValue;
intValue.addObserver(this); // Register interest
setValue(intValue.getValue()); // Change scrollbar position
}
// The handleEvent method check with the parent class (Scrollbar) to see
// if it wants the event, if not, just assume the scrollbar value has
// changed and update the observable int with the new position.
public boolean handleEvent(Event evt)
{
if (super.handleEvent(evt))
{
return true; // The Scrollbar class handled it
}
intValue.setValue(getValue()); // Update the observable int
return true;
}
// update is called whenever the observable int changes its value
public void update(Observable obs, Object arg)
{
setValue(intValue.getValue());
}
}

This may look like a lot of work, but watch how easy it is to create an applet with an IntScrollbar that modifies an ObservableInt and an IntLabel that displays one. Listing 30.8 shows an implementation of an applet that uses the IntScrollbet, the ObservableInt, and the IntLabel.

Listing 30.8 Source Code for ObservableApplet1.java.

import java.applet.*;
import java.awt.*;
public class ObservableApplet1 extends Applet
{
ObservableInt myIntValue;
public void init()
{
// Create the Observable int to play with
myIntValue = new ObservableInt(5);
setLayout(new GridLayout(2, 0));
// Create an IntScrollbar that modifies the observable int
add(new IntScrollbar(myIntValue,
Scrollbar.HORIZONTAL,
0, 10, 0, 100));
// Create an IntLabel that displays the observable int
add(new IntLabel(myIntValue));
}
}

You may notice when you run this applet that the label value changes whenever you update the scroll bar, yet the label has no knowledge of the scroll bar, and the scroll bar has no knowledge of the label.

Now, suppose you also want to allow the value to be updated from a TextField. All you need to do is create a subclass of TextField that modifies the ObservableInt. Listing 30.9 shows an implementation of an IntTextField.

Listing 30.9 Source Code for IntTextField.java.

import java.awt.*;
import java.util.*;
//
// IntTextField - a TextField that reads in integer values and
// updates an Observable int with the new value. This class
// is both a "view" of the Observable int, since it displays
// its current value, and a "controller" since it updates the
// value.
public class IntTextField extends TextField implements Observer
{
private ObservableInt intValue;
public IntTextField(ObservableInt theInt)
{
// Initialize the field to the current value, allow 3 input columns
super(""+theInt.getValue(), 3);
intValue = theInt;
intValue.addObserver(this); // Express interest in value
}
// The action for the text field is called whenever someone presses "return"
// We'll try to convert the string in the field to an integer, and if
// successful, update the observable int.
public boolean action(Event evt, Object whatAction)
{
Integer intStr; // to be converted from a string
try { // The conversion can throw an exception
intStr = new Integer(getText());
// If we get here, there was no exception, update the observable
intValue.setValue(intStr.intValue());
} catch (Exception oops) {
// We just ignore the exception
}
return true;
}
// The update action is called whenever the observable int's value changes.
// We just update the text in the field with the new int value
public void update(Observable obs, Object arg)
{
setText(""+intValue.getValue());
}
}

After you have created this class, how much code do you think you have to add to the applet? You add one line (and change GridLayout to have three rows). Listing 30.10 shows an implementation of an applet that uses an ObservableInt, an IntScrollbar, an IntLabel, and an IntTextField.

Listing 30.10 Source Code for ObservableApplet2.java.

import java.applet.*;
import java.awt.*;
public class ObservableApplet2 extends Applet
{
ObservableInt myIntValue;
public void init()
{
// Create the Observable int to play with
myIntValue = new ObservableInt(5);
setLayout(new GridLayout(3, 0));
// Create an IntScrollbar that modifies the observable int
add(new IntScrollbar(myIntValue,
Scrollbar.HORIZONTAL,
0, 10, 0, 100));
// Create an IntLabel that displays the observable int
add(new IntLabel(myIntValue));
// Create an IntTextField that displays and updates the observable int
add(new IntTextField(myIntValue));
}
}

Again, the components that modify and display the integer value have no knowledge of each other, yet whenever the value is changed, they are all updated with the new value.


Previous Page TOC Next Page

| Previous Chapter | Next Chapter |

|Table of Contents | Book Home Page |

| Que Home Page | Digital Bookshelf | Disclaimer |


To order books from QUE, call us at 800-716-0044 or 317-361-5400.

For comments or technical support for our books and software, select Talk to Us.

© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company