This chapter continues our adventure of exploring the Java packages. Two packages are covered here: java.io and java.util. The java.io package contains interfaces, classes, and methods that facilitate input and output of data to applets and applications. The java.util package is a mixed bag of utilities useful in writing Java programs. Its classes are used to manipulate dates, create hash tables, and directly manipulate stacks of objects, among other things.
The Java input and output package-java.io-is used to implement streams. Streams are covered in Chapter 8, "Tying It All Together: Threads, Exceptions, and More;" refer to that chapter if you have difficulty with some of the examples or information in this chapter. Here I cover the classes and methods that manipulate streams.
Streams are used to move groups of data from point A to the Java application or from the Java application to point B. At its simplest, transmitted data is in the form of a stream of bytes without formatting or identification. It is up to the Java application to reformat and reinterpret it in such a way that the data makes sense to the application.
Data traditionally has been stored in files on a disk somewhere. Moving data was a simple matter of opening a file, copying the data to memory, copying the needed portions from memory, and moving the data from memory to a new disk location under a new filename on a system that controlled all the steps of the process.
That scenario is certainly not the case these days. Data can be generated by users on a local network and loaded on a distributed system. It can come from just about any system in the world over the Internet; and from disk, tape, CD-ROM, and so on. The source of the data may be running any number of operating systems, which means that the data might be in a format foreign to the system running the Java application. Therefore, Java must be able to interpret data in many formats from many sources. It must be adaptable enough to handle this data without requiring code changes if the data stream origin changes. The classes and methods in java.io help make this happen.
Remember that the whole idea behind Java is to provide a non-platform-specific application language. This includes the movement and interpretation of data. The classes and methods in the java.io package allow great flexibility in sending and receiving data.
Java provides control over the flow of input streams using mark and reset (discussed in Chapter 8). However, not all forms of Java I/O allow the use of mark and reset.
The following sections discuss the various ways to move data in Java. Pick the one that best suits your needs, because each has advantages and disadvantages.
Input streams are streams of data that arrive from a source and are loaded into Java. Java doesn't care about the data's origins; it only cares about the data being available to send to the Java application. All the input stream classes throw the exception IOException. (Refer to the section in Chapter 8 called "Exceptions" for more information.)
All the input stream classes are based on the abstract class InputStream. The next section discusses the methods associated with that class.
InputStream is the abstract class that contains all the possible types of inputs to Java. Its subclasses use most or all of its methods. These methods are discussed in the "Input Streams" section of Chapter 8, but let's go over them again.
All of InputStream's subclasses can read data from an input stream. These reads are based on blocking, which allows the Java application to wait if the input stream is not continuous.
The skip method is used to bypass a fixed number of bytes of an input stream. This method is available to all of InputStream's subclasses.
The close method is used to shut down an input stream that is no longer being used. Files are closed automatically when the Java garbage collector finalizes and closes them. However, it is important to manually close the stream if the file is going to be opened again soon. You can't know exactly when the garbage collector will finally close the stream, and Java will not reopen an already open file.
The available method is used to determine whether the data is ready to be sent. Remember, the program will block if the data is not ready but a read request has been sent. If you put the read request in a separate thread, it won't matter if the block occurs; in that case it is not necessary to use available. Make sure, however, that this makes sense in the application. Using separate threads is not advisable if the whole application relies on the data to be sent.
The available method is not available for all subclasses of InputStream.
The mark method marks a point in an input stream to return to later. The markSupported method makes sure that the type of input stream allows mark. The reset method returns to the point marked by mark. These methods are not supported in all the subclasses of InputStream.
The subclasses of InputStream are ByteArrayInputStream, FileInputStream, FilterInputStream, PipedInputStream, SequenceInputStream, and StringBufferInputStream. Each is discussed in detail in the following sections.
FilterInputStream has four subclasses: BufferedInputStream, DataInputStream, LineNumberInputStream, and PushbackInputStream. These subclasses work in a unique way with FilterInputStream: They allow it to control the flow of a file to them. FilterInputStream does nothing by itself.
Think of FilterInputStream as providing the structure of the pipe for the flow of data while its subclasses chop the data up into small, usable bits. Picture FilterInputStream as the a pasta maker that squeezes out the dough while the subclass turns it into spaghetti or fettucine or lasagna noodles. The data is the dough. It is also possible to change the "mold," or subclass, midstream, just as you would change the form on the pasta maker. However, you don't have to stop the machine while changing the form; the data still flows through.
FilterInputStream's subclasses are widely used in writing Java programs. Following is a discussion of the subclasses. (Read the online documentation for a full list of each subclass's capabilities.)
BufferedInputStream is a subclass of FilterInputStream. It implements all the methods defined in the superclass InputStream.
Note |
BufferedInputStream is the only class that uses mark and reset correctly. |
Another key attraction to using BufferedInputStream is that it creates a read buffer, which is an array of bytes. This read buffer smooths out the stream of data so that reading can be continuous, as opposed to waiting for the input to reach the stream. The size of the buffer and the size of the reads can be controlled through BufferedInputStream's methods. Listing 12.1 is an example of BufferedInputStream and BufferedOutputStream.
Listing 12.1. An example of BufferedInputStream and BufferedOutputStream.
/* Notes:
This class implements buffered file input/output streams by extending
class BufferedInputStream. This is not a complete implementation of all
of FileInputStream (not enough constructors, or getFd()).
The main example simply copies the first 128 bytes of file one to
file two, then resets file one to the beginning and proceeds to copy
all of file one to file two. This has no practical purpose, but does
demonstrate that a BufferedFileInputStream class has been created that
implements mark and reset, which FileInputStream does not support.
Syntax:
MyMain <source file> <dest file>
*/
import java.io.*;
public class MyMain {
public static void main (String args[]) {
// declare buffered file input stream
BufferedFileInputStream bfis;
// declare buffered file output stream
BufferedFileOutputStream bfos;
// declare buffer
byte buf[];
int cnt;
// create 1024 byte buffer
buf = new byte[1024];
try {
//create buffered file input stream using first argument
//on command line
bfis = new BufferedFileInputStream(args[0]);
//create buffered file output stream using second argument
//on command line
bfos = new BufferedFileOutputStream(args[1]);
// mark beginning of file to be buffered for 128 bytes
bfis.mark(128);
// read 128 bytes from source file
bfis.read(buf,0,128);
// write buffered bytes to dest file
bfos.write(buf,0,buf.length);
// reset to beginning of file
bfis.reset();
// loop through source file reading 1024 bytes at a time
while (bfis.read(buf,0, 1024) != -1) {
// write buffer to dest file
bfos.write(buf, 0, buf.length);
}
// close source file
bfis.close();
// close source file
bfos.close();
}
// catch file not found errors (do nothing)
catch (FileNotFoundException e){}
// catch IO errors (do nothing)
catch (IOException e){}
}
}
// declare new class that extends buffered input stream
class BufferedFileInputStream extends BufferedInputStream {
// declare file input stream
FileInputStream f;
// override buffered input stream constructor
public BufferedFileInputStream(String filename) throws IOException {
// create buffered input stream using file input stream
super(new FileInputStream(filename));
}
}
// declare new class that extends buffered output stream
class BufferedFileOutputStream extends BufferedOutputStream {
// override buffered output stream
public BufferedFileOutputStream(String filename) throws
IOException {
// create buffered output stream using file output stream constructor
super(new FileOutputStream(filename));
}
}
DataInputStream is a subclass of FilterInputStream. It also implements the DataInput interface. Look at the following list of methods in DataInputStream, and the major use of the class becomes obvious (note that these same methods are used by the class RandomAccessFile):
readBoolean
readByte
readChar
readDouble
readFloat
readInt
readLine
readLong
readShort
readUTF
readUnsignedByte
readUnsignedShort
These methods are used to read primitive data types. Remember primitive data types from Chapter 6, "Fundamentals of the Java Language"? This class interprets primitive data types across platforms and makes them accessible to Java.
LineNumberInputStream is a subclass of FilterInputStream. This class keeps track of line numbers, which could be used to mark and reset data streams. LineNumberInputStream also can be used to nest the handling of input streams so that it is possible to simultaneously read the stream for other purposes and keep track of line numbers.
PushbackInputStream is a subclass of FilterInputStream. It creates a one-byte input buffer that allows the input stream to retreat one byte after it has been read. This makes it possible to test the next byte before taking action. Listing 12.2 is an example of PushbackInputStream.
Listing 12.2. An example of PushbackInputStream.
/* Notes:
This class implements a word search program. A word is defined as a
character string. It considered a word instead of a substring if the
word is followed by a space. The algorithm works its way through the
input stream. If it actually finds a word, it pushes the space
that delimited the word back on the stream so that it can be processed
if needed by other algorithms.
The main example accepts a string to search for and a filename to
search in as command line arguments. It will print "Word found" if
the word is successfully found, and "Word not found" if it does not.
Syntax:
MyMain <string to search for> <file to search>
*/
import java.io.*;
public class MyMain {
public static void main (String args[]) {
// declare FindWord object
FindWord f;
try {
// create FindWord object by passing it a FileInputStream object.
//File opened in second argument on command line.
f = new FindWord(new FileInputStream(args[1]));
// find word specified as first argument on command line.
if (f.find(args[0]))
// print out found message if word is found
System.out.println("Word found");
else
// print out not found message if word not found
System.out.println("Word not found");
}
// handle file not found exceptions
catch(FileNotFoundException e) {
System.err.println("ERROR: File not found");
System.err.println(" file = " + args[1]);
System.exit(2);
}
}
}
// declare class FindWord
class FindWord {
// declare PushbackInputStreamObject
PushbackInputStream pbs;
// declare FindWord constructor
public FindWord(InputStream in) {
// create pushback stream using passed in input stream
pbs = new PushbackInputStream(in);
}
// declare method to find word in stream
public boolean find (String word) {
// declare buffer to be used in stream reads
byte buf[];
int i = 0;
// declare booleans for word found and end-of-file
boolean found, EOF;
// create one byte array needed by stream read
buf = new byte[1];
// initialize word found variable to false
found = false;
// initialize end-of-file variable to false
EOF = false;
// declare string buffer so a space can be appended to search word
StringBuffer sbWord;
// create string buffer initialized to search word followed by a space
sbWord = new StringBuffer(word + " ");
// label for labeled continue inside loop
startOver:
// loop waiting for word found or end of file
while ((! found) && (! EOF)) {
// loop for matching search word to chars in input stream
for (i=0; i < sbWord.length(); i++) {
try {
// read char from stream checking for EOF
if (pbs.read(buf,0,1) == -1) {
// set EOF boolean if EOF reached
EOF = true;
}
// compare stream char to search word char
if ((char)buf[0] != sbWord.charAt(i)) {
// if different continue in OUTER while loop
continue startOver;
}
}
// handle IO exceptions
catch(IOException e){
// exit application if IO error occurred
System.exit(3);}
}
// word found if this point reached, push space back on stream
try {pbs.unread(buf[0]);}
// handle IO exceptions
catch(IOException e){
// exit application if IO error occurred
System.exit(4);}
// return true since word found
return(true);
}
// return false since word not found
return(false);
}
}
The ByteArrayInputStream class extends the InputStream class. This class is used to create an input stream from a buffer. The input stream is accessed in the number of bytes set by the programmer. The flow of data to the input stream is controlled by three variables:
ByteArrayInputStream allows use of the reset method, which resets the input stream back to the beginning. There is no mark method to mark a specific place in the stream.
The FileInputStream class extends the InputStream class. This class allows Java to read files. This will work only with sequential files, not with hash tables or indexed files. The file itself must be accessible to Java.
Be sure to explicitly close the file input stream if there are to be subsequent accesses to it after the end of file has been reached. mark and reset are not available to FileInputStream. The only way to return to a position is to skip to it and then read to find a desired piece of data. There is no way to know exact positions in the file directly with FileInputStream.
Refer to the section in Chapter 8 titled "Input Streams" for an example of the use of FileInputStream.
SequenceInputStream allows multiple files to be read in sequence and converted into a single stream. The first input is read through to the end of file, then the next file is read, and so on. The output is a byte array. The only methods available are read and close.
One reason to use SequenceInputStream is to receive multiple inputs, create one input stream, and pass the stream off to another class that has more data-manipulation methods available.
StringBufferInputStream is used to create an input stream from a buffer. The input stream is an array of string characters. The flow of data to the input stream is controlled by three variables:
This is similar to ByteArrayInputStream; the only difference is that ByteArrayInputStream inputs the stream as an array of bytes, whereas StringBufferInputStream creates the input stream as an array of string characters. With StringBufferInputStream, you can use the reset method, which resets the input stream to the beginning. There is no mark method to mark a specific place in the stream.
Once again, refer to Chapter 8 for a detailed discussion of output streams. I will do only a quick review here. This section discusses the java.io package and its output stream classes.
Output streams are data streams generated by a Java application or applet for use elsewhere. There are several ways to format the data for output: It can be piped, buffered, printed to a screen, stored in an array of bytes, or output to a file. Output stream classes perform the output duty.
All the output stream classes are based on the abstract class OutputStream. The next section discusses the methods associated with OutputStream.
OutputStream is the abstract class that contains all the possibilities of output from Java. Its subclasses are BufferedOutputStream, ByteArrayOutputStream, DataOutputStream, FileOutputStream, FilterOutputStream, PipedOutputStream, and PrintStream.
OutputStream's subclasses use most or all of its methods. These methods are discussed in Chapter 8, but a quick review never hurts.
The write method is used to write data to a stream. It varies for each class in its output. All write methods are the same in that they block, or wait, for the receiving device to catch up. All write methods also throw the exception IOException.
See the section titled "Input Streams" in Chapter 8 for an example of write.
The flush method is used to force any data buffered for the output stream to be written to the output device. This is the only way to override a block. This also may be the only way to write data if the receiving device requires it.
The close method closes an output stream. It is identical in logic to the close used for input streams. The Java garbage collector schedules a cleanup to the output streams when an applet or application closes. Be sure to manually call close if the application can't wait for the garbage collector.
FilterOutputStream has three subclasses: BufferedOutputStream, DataOutputStream, and PrintStream. These subclasses work in a unique way with FilterOutputStream; they allow it to control the flow of a file to them. FilterOutputStream does nothing by itself. (This is the output equivalent to FilterInputStream.)
Following is a discussion of the subclasses. Read the online documentation for a full list of each subclass's capabilities.
BufferedOutputStream implements all the methods defined in superclass OutputStream. BufferedOutputStream uses flush and can create a nested stream to allow other output streams to flush.
Another key attraction to using BufferedOutputStream is that it creates a write buffer in the form of an array of bytes. This write buffer smooths out the stream of data so that writing can be continuous, as opposed to waiting for the output to reach the stream. The size of the buffer and the size of the writes can be controlled through BufferedOutputStream's methods.
DataOutputStream also implements the DataOutput interface. It is the inverse of DataInputStream and is used to write primitive data types. Following is a list of associated methods:
writeBoolean
writeByte
writeChar
writeDouble
writeFloat
writeInt
writeLine
writeLong
writeShort
writeUTF
writeUnsignedByte
writeUnsignedShort
DataOutputStream is used with BufferedInputStream to pull and push primitive data types across platforms.
PrintStream is used to write formatted data to the user's screen. PrintStream is the actual class used to perform the printing in methods, like this:
System.out.print()
System.out.println()
It is possible to do autoflushing with PrintStream. The two constructors to use with PrintStream are
PrintStream(OutputStream)-Creates a new PrintStream
PrintStream(OutputStream, boolean)-Creates a new PrintStream, with autoflushing
PrintStream formats primitive data types for display to the screen. The following methods are used:
print(Object)
print(String)
print(char[])
print(char)
print(int)
print(long)
print(float)
print(double)
print(boolean)
println()
println(Object)
println(String)
println(char[])
println(char)
println(int)
println(long)
println(float)
println(double)
println(boolean)
See Listing 12.3 for an example of the use of PrintStream.
Listing 12.3. An example of the use of PrintStream.
/* Notes:
This class implements a generic write class that can write any arbitrary
object type to a file. The constructor is used to open the file. The
resulting output stream is used as the constructor for the PrintStream
class. PrintStream is used because it can write arbitrary objects.
PrintStreams implicitly use "toString" methods within objects to
convert objects to strings.
An arbitrary class called PhoneInfo is created simply for the purpose of
demonstrating PrintStream's ability to write any type of object.
The main example simply populates a PhoneInfo array and then uses the
WriteObjToFile class to actually write the information to the file. The
filename to write to is specified on the command line.
Syntax:
MyMain <dest file>
*/
import java.io.*;
public class MyMain {
public static void main (String args[]) {
int i;
// declare WriteObjToFile object
WriteObjToFile w;
// create info to populate phone info database
String[] s = {"Roy Rogers", "(111) 111-1111",
"Jesse James", "(222) 222-2222",
"Wyatt Earp", "(333) 333-3333"};
// declare PhoneInfo array
PhoneInfo[] p;
// create PhoneInfo array
p = new PhoneInfo[s.length/2];
// loop through PhoneInfo array creating phoneinfo objects
for (i=0; i<s.length; i+=2) {
p[i/2] = new PhoneInfo(s[i],s[i+1]);
}
try {
// create WriteObjToFile object
w = new WriteObjToFile(args[0]);
// loop through phone database passing objects to WriteObjToFile
for (i=0; i<p.length; i++) {
w.write(p[i]);
}
}
// catch IO exceptions
catch(IOException e) {
System.err.println("ERROR: IO Exception");
System.exit(2);
}
}
}
// declare PhoneInfo class
class PhoneInfo {
// declare information variables
String name, phoneNum;
// declare PhoneInfo constructor
public PhoneInfo(String name, String phoneNum) {
// initialize name based on constructor argument
this.name = new String(name);
// initialize phone number based on constructor argument
this.phoneNum = new String(phoneNum);
}
// declare toString method for returning string representation of instance
public String toString() {
// return string
return("Name: " + name + "\nPhone Number: " + phoneNum);
}
}
// declare WriteObjToFile class
class WriteObjToFile {
// declare PrintStream object
PrintStream ps;
// declare constructor
public WriteObjToFile(String filename) throws IOException {
try {
// create printstream using FileOutPutStream as the stream to write to
ps = new PrintStream(new FileOutputStream(filename));
}
// handle file not found errors
catch (FileNotFoundException e) {
System.err.println("ERROR: File not found");
System.err.println(" File = " + filename);
System.exit(2);
}
}
// declare method to write arbitrary objects
public void write (Object o) {
// write object to output stream
ps.println(o);
}
}
ByteArrayOutputStream, the counterpart of ByteArrayInputStream, is used to create an input stream from a buffer-that is, it pushes Java output into a buffer. The buffer grows as needed.
The flow of data to the output stream is controlled by two variables:
With ByteArrayOutputStream, you can use the reset method, which resets the output stream buffer so that the space of the buffer is not released and can be reused. There is no mark method to mark a specific place in the stream.
FileOutputStream, the counterpart to FileInputStream, enables Java to write to files. The file and directory must be accessible to Java, which is a security issue.
Warning |
Be sure to explicitly close the file output stream if there are to be subsequent accesses to it after the end of file has been reached. Otherwise, the file will not be properly terminated with an end of file marker and you will get errors. |
The only methods available to FileOutputStream are the following:
close()-Closes the stream
finalize()-Closes the stream when garbage is collected
getFD()-Returns the file descriptor associated with this stream
write(int)-Writes a byte of data
write(byte[])-Writes an array of bytes
write(byte[], int, int)-Writes a subarray of bytes
PipedOutputStream supports moving data to and from threads that are piped together using the PipeInputStream. Because PipedOutputStream is connected to the PipeInputStream, the two must work together.
The methods associated with these classes are
close()-Closes the stream
connect(PipedInputStream)-Connects this output stream to a receiver
write(int)-Writes a byte
write(byte[], int, int)-Writes a subarray of bytes
The java.io package includes two miscellaneous I/O classes for dealing with files: File and RandomAccessFile.
The File class is used to represent a filename on a host system. It cannot read or write to the file, but it can get information about the file such as its name, path, and so on. It works as an abstract way of dealing with a platform-specific file.
RandomAccessFile implements both DataInput and DataOutput interfaces. It combines both input and output access to a file within the security confines of Java. The numerous methods associated with it read and write primitive data types, close the file, and locate the file. RandomAccessFile also allows skip.
The java.util package is sort of a catch-all for handy utilities. It contains the following classes:
BitSet
Date
Dictionary
Hashtable
Observable
Properties
Random
Stack
StringTokenizer
Vector
As you can see, this is certainly a mixed bag of classes. Read on for a description of each one. See the online documentation for details on all the methods associated with each class.
The Bitset class creates a set of bits that can grow as needed. It has methods that can manipulate the bits in the set. Listing 12.4 is an example of how to use Bitset.
Listing 12.4. An example using Bitset.
/* Notes:
This class does 7 bit even parity generation and checking.
*/
import java.util.*;
public class MyMain {
public static void main (String args[]) {
Parity p; // declare parity object
p = new Parity((byte)100); // create parity object
p.genParity(); // generate parity bit
if (p.chkParity()) // check for parity error
System.out.println("no parity error");
else
System.out.println("parity error");
}
}
class Parity { // declare class parity
BitSet b; // declare bitset object
public Parity(byte data) { // declare parity constructor
int i;
b = new BitSet(8); // create new bitset object length 8
for (i=0; i<7; i++) { // copy input data bits to bitset
if (((1 << i) & data) == 1) { // test if data bit is 1
b.set(i); // set corresponding bitset bit
}
}
}
public void genParity() { // method to generate parity bit
int i, cnt;
cnt = 0;
for (i=0; i<7; i++) { // loop through bitset counting data bits
if (b.get(i)) {
cnt++;
}
}
if ((cnt % 2) == 0) // test if even or odd number data bits set
b.clear(7); // even, clear parity bit
else
b.set(7); // odd, set parity bit
}
public boolean chkParity() { // method to check for parity errors
int i, cnt, parityBit;
cnt = 0;
for (i=0; i<7; i++) { // loop through bitset counting data bits
if (b.get(i)) {
cnt++; // increment count of set data bits
}
}
if (b.get(7)) // get setting of parity bit
parityBit = 1; // parity bit set
else
parityBit = 0; // parity bit not set
if (((cnt %2) ^ parityBit) == 0) // check if parity bit set correctly
return(true); // no parity error detected
return(false); // parity error detected
}
}
Date provides methods for examining and manipulating date and time. Time in Java is measured in milliseconds since January 1, 1970. Java attempts to handle time from the system with which it is interacting. UTC is Coordinated Universal Time, which seems to be the worldwide standard.
Note |
The Date class does not work as documented in the Java API. Although you should be able to work with dates since 1900, dates before January 1, 1970 are generally not usable. |
See Table 12.1 for a list of methods to see the full scope of
this class.
UTC(int, int, int, int, int, int) | Calculates a UTC value from YMDHMS |
after(Date) | Checks whether this date comes after the specified date |
before(Date) | Checks whether this date comes before the specified date |
equals(Object) | Compares this object against the specified object |
getDate() | Returns the day of the month |
getDay() | Returns the day of the week |
getHours() | Returns the hour |
getMinutes() | Returns the minute |
getMonth() | Returns the month |
getSeconds() | Returns the second |
getTime() | Returns the time in milliseconds since the epoch |
getTimezoneOffset() | Returns the time zone offset in minutes for the current locale that is appropriate for this time |
getYear() | Returns the year after 1900 |
hashCode() | Computes a number that is used when storing objects in hash tables. |
parse(String) | Given a string representing a time, parses it and returns the time value |
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 |
setYear(int) | Sets the year |
toGMTString() | Converts a date to a String object using the Internet GMT conventions |
toLocaleString() | Converts a date to a String object, using the locale conventions |
toString() | Converts a date to a String object using the UNIX time conventions |
It is important to remember that months start with 0, which is January, and end with 11, which is December. The days of the week also start with 0, which is Sunday, and go through 6, which is Saturday. Dates of the month are normal.
Note |
Dates that are not within the normal range parameters are interpreted through consecutive iterations of the date range. For example, 32 January is recognized as 1 February. |
Listing 12.5 is an example of the use of the Date class.
Listing 12.5. Using Date.
/* Notes:
Java's time handling utilities in java.util do not seem to handle
dates before 1970 even though it is documented as 1900.
Usage: date specified on the command line as first argument:
MyMain "18 Apr 1972"
*/
import java.util.*;
public class MyMain {
public static void main (String args[]) {
BDay b; // declare birthday object;
b = new BDay(args[0]); // create birthday object initialized
// to first date specified on command line
b.printBDayInfo(); // print out resulting birthday information
}
}
class BDay { // declare new class birthday
String sign; // declare string object to hold Astrological sign
Date d;
// declare date object to hold date of birth
public BDay(String date) { // declare constructor for this class
Date c; // declare date object used for comparisons
int year; // declare int to hold year of birth
d = new Date(date);
// create new date object initialized to birthday
year = d.getYear(); // get year from birth date
while (true) { // loop finding Astrological sign
c = new Date("20 Jan " + year);
// create comparison date of end of capricorn period
if (d.before(c)) {
// check if birth date came before capricorn
sign = new String("Capricorn"); // if so, set sign to capricorn
break; &nbs p;// break out of while loop
}
c = new Date("19 Feb " + year); // repeat above for aquarius
if (d.before(c)) {
sign = new String("Aquarius");
break;
}
c = new Date("21 Mar " + year); // repeat above for pisces
if (d.before(c)) {
sign = new String("Pisces");
break;
}
c = new Date("20 Apr " + year); // repeat above for aries
if (d.before(c)) {
sign = new String("Aries");
break;
}
c = new Date("21 May " + year); // repeat above for taurus
if (d.before(c)) {
sign = new String("Taurus");
break;
}
c = new Date("21 Jun " + year); // repeat above for gemini
if (d.before(c)) {
sign = new String("Gemini");
break;
}
c = new Date("23 Jul " + year); // repeat above for cancer
if (d.before(c)) {
sign = new String("Cancer");
break;
}
c = new Date("23 Aug " + year); // repeat above for leo
if (d.before(c)) {
sign = new String("Leo");
break;
}
c = new Date("23 Sep" + year); // repeat above for virgo
if (d.before(c)) {
sign = new String("Virgo");
break;
}
c = new Date("23 Oct " + year); // repeat above for libra
if (d.before(c)) {
sign = new String("Libra");
break;
}
c = new Date("22 Nov " + year); // repeat above for scorpio
if (d.before(c)) {
sign = new String("Scorpio");
break;
}
c = new Date("22 Dec" + year); // repeat above for sagittarius
if (d.before(c)) {
sign = new String("Sagittarius");
break;
}
c = new Date("31 Dec" + year); // catch end case for capricorn
if (d.before(c)) {
sign = new String("Capricorn");
break;
}
}
}
// declare method for printing birthdate information
public void printBDayInfo() {
Date t,c;
// declare date objects for todays date and for comparisons
t = new Date(); // obtain todays date
t.setHours(0);
// set hours of todays date to zero
t.setMinutes(0);
// set minutes of todays date to zero
t.setSeconds(0);
// set seconds of todays date to zero
c = new Date(t.getYear(), (d.getMonth()-1), d.getDate());
// create date object for this years birthday
System.out.println("You were born on: " + (d.getMonth()+1) + "/" +
d.getDate() + "/" + d.getYear()); // print out birth date
System.out.println("Your sign is: " + sign);
// print out Astrological sign
if (c.before(t))
// check if birthday already occurred this year
System.out.println("Your birthday has already occurred this year");
// print out birthday already occurred
else
if (c.after(t))
// check if birthday is yet to come this year
System.out.println("Your birthday has yet to occur this year");
// print out birthday is yet to come
else
System.out.println("TODAY IS YOUR BIRTHDAY!");
// if neither before today or after today, it must be today
}
}
As you can see, there are numerous ways to use Date's methods to manipulate and examine dates.
Dictionary is an abstract class. Its only current subclass is Hashtable. Dictionary is used to create a way of organizing objects. (See the following "Hashtable" section for the implementation of Dictionary.)
The idea is to access objects nonsequentially. There are keys to identify and access objects. Think of Dictionary as storing objects in a structure similar to an indexed file.
Hashtable extends Dictionary and is used to map keys to values. The methods associated with Hashtable enable the placing, locating, and retrieving of objects that are used as keys. The Properties class extends Hashtable.
Properties is a class that loads an input stream into a hash table. It also allows saves and gets from the hash table.
Random contains methods that create pseudo-random numbers. These numbers can be either Gaussian, Double, Float, Int, or Long. The seed, or starting point, can be reset anytime using the appropriate method.
Vector is the abstract parent class for Stack. You will use the vector class to implement a growable array of objects.
Stack extends the Vector class. It creates a last in, first out (LIFO) stack of objects. See Listing 12.6 for an example using Stack.
Listing 12.6. An example using Stack.
/* Notes:
Either java or NT java will not accept "-" as a command line argument.
The user must specify "--" to get the program to simply see "-".
This program works by specifying an RPN string on the command line.
*/
import java.util.*;
public class MyMain {
public static void main (String args[]) {
// declare RPN calculator object
RPncalculator c;
// create RPN calculator passing command line arguments
c = new RPncalculator(args);
}
}
// declare new RPN calculator class
class RPncalculator {
// declare Stack object
Stack s;
// declare generic object
Object o;
// RPN Calculator constructor which also solves equation string passed to it
public RPncalculator(String args[]) {
int i,j;
int[] num;
// declare object to hold current arithmetic operator
Character operator;
// create new stack
s = new Stack();
// create two integer array to hold operands
num = new int[2];
// loop through equation strings
for (i=0; i < args.length; i++) {
// handle number format exceptions
try {
// try converting string to number and push on stack if successful
s.push(new Integer(args[i]));
}
// string is not a number, must be an operator
catch(java.lang.NumberFormatException e) {
// convert string operator to character
operator = new Character(args[i].charAt(0));
// loop through operands operator will be used with
for (j=0; j<2; j++) {
// not enough operands on stack, generate error
if (s.empty()) {
System.err.println("ERROR: stack is empty");
System.exit(2);
}
// extract operand from stack
num[j] = ((Integer)s.pop()).intValue();
}
// switch based on operator
switch (operator.charValue()) {
// operator is "+", add operands
case '+':
s.push(new Integer(num[1] + num[0]));
break;
// operator is "-", subtract operands
case '-':
s.push(new Integer(num[1] - num[0]));
break;
// operator is "*", multiply operands
case '*':
s.push(new Integer(num[1] * num[0]));
break;
// operator is "/", divide operands
case '/':
s.push(new Integer(num[1] / num[0]));
break;
default:
// unknown operator, print error message
System.err.println("ERROR: invalid operator: " + operator);
System.exit(3);
break;
}
}
}
// value remaining on stack must be answer, print to screen
System.out.println(((Integer)s.pop()).intValue());
}
}
The StringTokenizer class provides a set of methods for converting an input stream into a set of tokens. Methods are provided for specifying which characters make up a token, which characters serve as token delimiters, whether case should be considered, and whether numeric characters should be converted to numbers. The StringTokenizer class also understands the /* */ comment-naming conventions of C, C++, and Java as well as the // used in C++ and Java.
The StringTokenizer class specifies all the parts that determine a token and then retrieves a single token in its entirety piece by piece from an input stream. This can be used for anything from splitting a simple sentence into individual words to processing something as complex as Java source code. (See the online documentation for details of the individual methods.)
Java provides several ways to deal with moving data through its applications. The java.io package contains classes that handle the input and output of data. The data arrives in a stream and can be sliced and diced in various ways by the subclasses of the abstract classes InputStream and OutputStream.
The data stream may block, or wait, if the flow is not continuous. Both input and output classes provide buffers to smooth this flow. All input classes are able to skip a certain number of bytes forward in the stream; some classes can mark a spot and then reset the stream to that spot. Some classes store the data in byte arrays, and one class stores it in a character string array.
Data accessed or stored by Java must originate or be placed in an area that is Java accessible. Much of the Java security schema relies on the segmentation of Java data from other system data. Therefore, it would not be a good idea to put a copy of your password file in this area.
The java.io package doesn't care where data originates; it only cares about what the programmer wants to do with it. The data-manipulation classes enable Java to be truly non-platform-specific, even when manipulating data.
The java.util package contains classes that add functionality to Java. These classes include a date manipulator (Date) and a bitset creator and manipulator (Bitset). There are also classes that provide a mechanism for a hash table structure of an object file (Dictionary and Hashtable). There is a pseudo-random number generator (Random). Another class creates a stack of objects (Stack), and another turns a string of characters into tokens (StringTokenizer).