a b c"d
In previous chapters, we have systematically ignored the fact that string are objects in Java; there was no need to worry about this point. For the curious, we now reveal that a string is in fact saved in computer storage as an array of characters. The initialization,
String s = "abcd";creates this object in storage:
This picture should make clear why the fundamental operations on strings are length and charAt. For example, s.length() returns 4, the length of the underlying array of characters, and s.charAt(2) returns 'c', because it is the character at element 2 of the underlying array.
Also, since strings are objects, it is no surprise that operations on strings are written as method invocations, where the string comes first, followed by a dot, followed by the operation (method) name, followed by arguments (if any), e.g., s.charAt(2). At this point, you should study Table 5 of Chapter 3, which lists many of the methods associated with class String.
It is possible to create a string directly from an array of characters:
char[] r = new char[2]; r[0] = 'a'; r[1] = 'b'; String u = new String(r); String v = "ab";The above statements create the string, "ab", and assign its address to t:
It is crucial that a separate object, a3, was created for u, because assignments to r's elements must not affect u. Also, a separate string is created for v. For this reason, the expression, u == v, evaluates to false, because the two variables denote different objects! In contrast, the equals method produces a different answer---u.equals(v) computes to true (as does v.equals(u)), because equals compares the sequences of characters within the two objects.
Perhaps the most important feature of String is this:
At this point, if we assigned,
s = u;then s's cell would hold the address, a6, also. This is no disaster, because the object at a6 cannot be altered---it stays constant at "abcde". For this reason, we need not worry that character arrays underlie the strings we create, and we can pretend that strings are primitive values, just like numbers. We return to this illusion for the remainder of the text.
(BeginFootnote: Java provides a version of strings that is mutable, that is, you can assign to the elements of the string. It is called a StringBuffer and is found in the package, java.lang. EndFootnote.)
With the assistance of the length and charAt methods, we can write loops that check two strings for equality, extract a substring from a string, find one string inside another, and so on. These are fundamental activities, and many of them are already written as methods that belong to class String in java.lang. Table 9 in Chapter 3 lists some of the most useful of these methods.
Our applications that read input have been designed to read one word---one token---per input line. But it is more convenient for a programmer to type multiple tokens on the same line. For example, a person would prefer to type her first, middle, and last names on one line rather than on three. And a user of the change-making program in Figure 2, Chapter 4, would prefer to type $13.46 as the program's input rather than 13 on one line and 46 on another.
Of course, we can write an auxiliary method that decomposes a string into its tokens, but this task arises often, so the Java designers wrote a helper class, class StringTokenizer (found in the package, java.util), that contains methods for decomposing strings into tokens.
Perhaps we have a string, s, that contains several tokens that are separated by special characters, called delimiters. For example, a string, Lucy MacGillicutty Ricardo, contains the tokens Lucy, MacGillicutty, and Ricardo, separated by blank spaces; the blank serves as the delimiter. We construct a string tokenizer object that extracts the tokens as follows:
String s = "Lucy MacGillicutty Ricardo"; StringTokenizer t = new StringTokenizer(s, " ");The string tokenizer object is constructed by the second statement, where the string to be disassembled and the character that is the delimiter are supplied as the two parameters. Here is a picture of the storage configuration:
To extract the first token from the string tokenizer object, we state,
String first_name = t.nextToken();The nextToken method asks t to examine the string it holds, skip over leading delimiters (if any), and extract the maximal, nonempty sequence of characters until a delimiter is encountered. In the example, this assigns "Lucy" to first_name,
and we see that the text within the string tokenizer is shortened. Note that the delimiter, the blank space, is left attached to the front of the text within the string tokenizer.
When we extract the second token,
String middle_name = t.nextToken();this skips over the leading blank, assigns "MacGillicutty" to middle_name, and leaves the object at a1 holding the string, " Ricardo". This assignment,
String last_name = t.nextToken();assigns "Ricardo" to last_name; the two leading blanks are discarded, and an empty string remains in the string tokenizer. If we attempt to extract a fourth token, the result is a NoSuchElementException, because no nonempty string can be returned.
Table 1 presents some of most useful methods for string tokenizers.
TABLE 1: string tokenizer methods=====================================
class StringTokenizer | |
Constructor | |
new StringTokenizer(String text, String delim) | Constructs a string tokenizer which extracts tokens from text, using as its separators all the characters listed in string delim. |
Methods | |
nextToken(): String | Examine the text held in the tokenizer: Remove leading delimiters, and remove the maximal, nonempty sequence of characters until a delimiter or the end of the text is encountered. Return the maximal, nonempty sequence of characters as the result. |
nextToken(String new_delimiters): String | Like nextToken(), but first reset the delimiters used by the string tokenizer to the ones listed in new_delimiters. |
hasMoreTokens(): boolean | Return whether the text held in the string tokenizer has any more tokens, based on the delimiters the tokenizer currently holds. |
countTokens(): int | Return the number of tokens within the text held in the string tokenizer, based on the delimiters the tokenizer currently holds. |
ENDTABLE============================================================
Here is a second example. Perhaps we wish to extract the tokens, 13 and 46 from the string, $13.46. We do so most effectively with this sequence of statements:
String s = "$13.46"; StringTokenizer t = new StringTokenizer(s, "$."); String dollars = t.nextToken(); String cents = t.nextToken();The second statement creates a string tokenizer that considers both $ and . to be legal delimiters. A string tokenizer can use multiple distinct delimiters, and the delimiters are packaged as a string to the tokenizer's constructor method. The third statement assigns "13" to dollars, because the leading symbol, $, is a delimiter and leading delimiters are always skipped over. The "13" is extracted and the . is detected as a delimiter. Next, cents is assigned 46, and the end of the string is encountered.
String x = "12,3,4"; StringTokenizer y = new StringTokenizer(x, ","); String s = y.nextToken(); int i = new Integer( y.nextToken() ).intValue() + new Integer( y.nextToken() ).intValue(); System.out.println(s + i);
String x = "12!3,4"; StringTokenizer y = new StringTokenizer(x, ","); String s = y.nextToken(); int i = new Integer( y.nextToken() ).intValue(); StringTokenizer t = new StringTokenizer(s,"!"); String a = t.nextToken(); int j = new Integer( t.nextToken() ).intValue(); System.out.println(i+j);
String input = JOptionPane.showInputDialog("Type a dollars, cents amount for change: $:");and uses a string tokenizer to assign values to variables dollars and cents.
A file is indeed one long sequence; for example, these three text lines,
Hello to you! How are you? 49would be stored as a character file as this long sequence:
Hello to you!\nHow are you?\n 49\nEvery symbol, including the exact quantity of blanks, are faithfully reproduced in the sequence. Newline characters ('\n') mark the ends of lines. If you create a character file with a PC, the MS-based operating system marks the end of each line with the two-character sequence, \r\n, e.g.,
Hello to you!\r\nHow are you?\r\n 49\r\nThe examples in this chapter are written so that this difference is unimportant.
If you could see with your own eyes the characters saved on the magnetic disk, you would not see letters like H and e. Instead, you would see that each character is translated to a standard sized, numeric coding. One standard coding is ASCII (pronounced ``as-key''), which uses one byte to represent each character. Another coding is Unicode, which uses two bytes to represent a character. (Unlike ASCII, Unicode can represent accented and umlauted letters.) We will not concern ourselves with which coding your computer uses to encode characters in a character file; these details are hidden from us by the Java classes that read and write files.
A binary file is a sequence of ones and zeros. Binary files hold information that is not keyboard-based. For example, if one wishes to save information about the address numbers inside the computer's primary storage, then a binary file is a good choice. Also, purely numerical data is often stored in a binary file: Inside the computer, an integer like 49 is used in its binary representation, 11001, and not in its character representation (the characters '4' and '9'). It is easier for the computer to do arithmetic on binary representations, and programs that work extensively with files of numbers work faster when the input and output data are saved in binary files---this saves the trouble of converting from character codings to binary codings and back again.
The Java language even allows a program to copy objects in computer storage to a binary file; this proves especially useful when one wants to archive the contents of an executing program for later use.
When we obtain input information from a file, we say that we read from the file; when we deposit output information into a file, we write to it. When we begin using a file, we say that we open it, and when we are finished using the file, we close it. The notions come from the old-fashioned concept of a filing cabinet whose drawer must be opened to get the papers (``files'') inside and closed when no longer used. Indeed, a computer must do a similar, internal ``opening'' of a disk file to see its contents, and upon conclusion a ``closing'' of the disk file to prevent damage to its contents.
Files can be read/written in two ways:
When a Java program uses a sequential file, it must state whether the file is used for
From this point onwards, we use the term sequential file to mean a sequential file of (codings of) characters. We will not deal with binary files in this text.
The package, java.io, contains classes that have methods for file input and output. To write to a sequential character file, we construct an object that connects to the file and deposits text into it; say that the file that will hold the output is named test.txt:
Filewriter w = new FileWriter("test.txt");A FileWriter object holds the physical address of the named disk file and writes individual characters to the file.
PrintWriter outfile = new PrintWriter(w);The PrintWriter object accepts print(E) and println(E) messages, where it translates the argument, E, into a sequence of characters and sends the characters, one by one, to the FileWriter object, w. For example,
outfile.println("Hello to you!");Appends the string, "Hello to you!", followed by a newline character, into file test.txt.
Figure 2 displays an example application that writes three lines of characters into the sequential file named test.txt.
FIGURE 2: sequential file output======================================== import java.io.*; /** Output1 writes three lines of characters to file test.txt */ public class Output1 { public static void main(String[] args) throws IOException { PrintWriter outfile = new PrintWriter(new FileWriter("test.txt")); outfile.println("Hello to you!"); outfile.print("How are"); outfile.println(" you?"); outfile.println(47+2); outfile.close(); } } ENDFIGURE==============================================================The application's output-view object is outfile, and it can be constructed in just one statement as
PrintWriter outfile = new PrintWriter(new FileWriter("test.txt"));
When the FileWriter object is constructed, it locates and opens the file, "test.txt", in the local directory (folder); if the file does not exist, a new, empty file with the name is created and opened. Files in other directories can be similarly opened provided a correct path name is specified, e.g., "C:\\Fred\\DataFiles\\test.txt" on an MS-based file system or "/home/Fred/DataFiles/test.txt" on a Unix-based system. (Recall that we must use \\ to stand for one backslash in a Java string.)
The output-view object possesses methods, println and print, for writing strings and numbers, and a method, close, for closing a file. (As always, the println method appends a newline character, '\n', to the end of the string that it writes.)
The program in Figure 2 writes three lines of text to test.txt, closes the file, and terminates. You can read the contents of test.txt with a text editor, and you will see
Hello to you! How are you? 49The former contents of test.txt, if any, were destroyed.
The program begins with import java.io.*, which is the package where the new classes live, and main's header line states throws IOException, which the Java compiler requires whenever file input/output appears. The phrase is a warning that that output transmission via might lead to an error (exception), e.g., the disk drive fails in the middle of output transmission. IOExceptions can be processed with exception handlers, which we first studied in Chapter 4. We employ exception handlers later in the chapter.
It is perfectly acceptable to use multiple output files within the same program, and it is done in the expected way:
PrintWriter file1 = new PrintWriter(new FileWriter("test.txt")); PrintWriter file2 = new PrintWriter(new FileWriter("AnotherFile.out")); ... file1.println("abc"); file2.print("c"); file2.println(); file1.close(); ...The above code fragment creates two objects that write information to two files, test.txt and AnotherFile.out.
FileReader r = new FileReader("test.txt");This constructs a FileReader object that holds the file's physical address and can read individual characters from the file. (If the file cannot be found, new FileReader("test.txt") generates an input-output exception.)
BufferedReader infile = new BufferedReader(r);The BufferedReader object uses the FileReader object to collect a full line of characters, which it forms into a string. The message, infile.readLine(), returns the string as its result.
As an example, we write an application that reads the contents of a sequential file whose name is supplied by the user at the keyboard and copies the file, f, line by line, into another file, f.out. Figure 3 shows the application, class CopyFile.
FIGURE 3: program that copies a file================================== import java.io.*; /** CopyFile copies the contents of an input file, f, * whose name is supplied by the user, into an output file, f.out */ public class CopyFile { public static void main(String[] args) throws IOException { String f = JOptionPane.showInputDialog("Input filename, please: "); // construct the view object that reads from the input file: BufferedReader infile = new BufferedReader(new FileReader(f)); // construct the view object that writes to the output file: PrintWriter outfile = new PrintWriter(new FileWriter(f + ".out")); while ( infile.ready() ) // are there more lines to read in infile? { String s = infile.readLine(); outfile.println(s); } infile.close(); outfile.close(); } } ENDFIGURE================================================================
The statement, infile = new BufferedReader(new FileReader(f)), creates a BufferedReader object that remembers the address of the filename, f, supplied by the user. In a similar fashion, outfile = new PrintWriter(new FileWriter(f + ".out")) creates an object that knows the address of the output file.
The while-loop within main uses a new method, infile.ready(), to determine if there are lines of text waiting to be read from infile. If so, then another line is read by s = infile.readLine() and is written by outfile.println(s). (Note: if the readLine method attempts to read a string from a file that is exhausted, it returns null as its result.)
Errors can arise when we use disk files. For example, say that we tell the program to read a nonexistent file, fred. The program stops at the assignment to infile (Line 10) and announces:
java.io.FileNotFoundException: fred (No such file or directory) ... at java.io.FileReader.The error, java.io.FileNotFoundException, is an example of an IOException---the program has ``thrown'' an exception, as was warned in the header line of main. Such exceptions can be handled by exception handlers, as we see in the examples in the next section.(...) at CopyFile.main(CopyFile.java:10)
Here is an example: Recall that the change-making application, MakeChange, from Figure 3, Chapter 3, calculates the change one extracts from dollars-and-cents amounts. We can revise this program so the user starts it first and decides next what the dollars and cents inputs might be. When the revised application is started, this request appears in the command window:
The user interacts with the application by typing at the keyboard, say, 3, and pressing the newline key; almost immediately, she sees another prompt:
Perhaps the user types 46 and newline. The answer then appears within the same window:
In Java, System.in is the object that is connected to the computer's keyboard for interactive input. Unfortunately, System.in reads just one key press at a time and not an entire line. (For example, when the user types 4 then 6 then newline, this constitutes three key presses.) So, we must compose System.in with additional objects to read one full line of text:
new InputStreamReader(System.in)
new BufferedReader(new InputStreamReader(System.in))Like the BufferedReader objects seen earlier, this object has a method named readLine that can read one full line of input.
The input behavior we saw in the earlier demonstration is caused by these statements,
System.out.print("Type a dollars amount: "); String dollars = keyboard.readLine(); System.out.print("Type a cents amount: "); String cents = keyboard.readLine();The first statement prints a partial line, which signals the user to type the dollars amount, and the second statement grabs what the user types. The third and fourth statements act similarly.
Here is the modified version of the change-making program:
import java.io.*; /** MakeChange2 calculates change for a dollars-and-cents amount. * input: a dollars amount and a cents amount, both integers * output: the listing of the needed coins */ public class MakeChange2 { public static void main(String[] args) throws IOException { BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Type a dollars amount: "); // print a partial line String dollars = keyboard.readLine(); // read one full line of typed input System.out.print("Type a cents amount: "); // do it again.... String cents = keyboard.readLine(); int money = (100 * new Integer(dollars).intValue()) + new Integer(cents).intValue(); // the remainder of the program stays the same: System.out.println( "quarters = " + (money/25) ); money = money % 25; System.out.println( "dimes = " + (money/10) ); money = money % 10; System.out.println( "nickels = " + (money/5) ); money = money % 5; System.out.println( "pennies = " + money ); } }
String tokenizers and disk file objects can help create input views that hide all details about file processing from the other components of an application. A standard example is payroll processing: Perhaps a company keeps its weekly payroll as a sequential file where each line contains an employee's name, hours worked, and hourly payrate, formatted as follows:
Fred Mertz|31|20.25 Lucy Ricardo|42|24.50 Ethel Mertz|18|18.00 !The sequence of lines is terminated by a special symbol, say, !. A payroll application reads the lines one by one, extracts the tokens from each line, converts them to names and numbers, and calculates paychecks. The effort spent on extracting names and numbers is best placed into a class that acts as the application's input view.
For the payroll application, the input view's responsibilites are to extract a name, hours worked, and payrate from each input record (line). Table 4 shows the interface for the payroll application's input view.
TABLE 4: input view interface======================================
class PayrollReader | |
Methods | |
getNextRecord(): boolean | Attempt to read the next payroll record from the input file. Returns whether or not a properly formatted record was successfully read. |
nameOf(): String | Return the name of the employee of the current payroll record. |
hoursOf(): int | Return the hours worked of the employee of the current payroll record. |
payrateOf(): double | Return the payrate of the employee of the current payroll record. |
close() | Close the payroll file. |
ENDTABLE============================================================
We can write an interface for the payroll application's output view as well, which will print the paychecks that are calculated. (Call the output view class PayrollWriter.) Based on these interfaces, we might write the controller for the payroll application that appears in Figure 5.
FIGURE 5: controller for payroll application============================ /** Payroll prints a file of paychecks from an input payroll file. */ public class Payroll { public static void main(String[] args) { String in_name = JOptionPane.showInputDialog("Please type input payroll name:"); String out_name = JOptionPane.showInputDialog("Please type output payroll name:"); if ( in_name != null && out_name != null ) { processPayroll(in_name, out_name); } System.out.println("finished"); } private static void processPayroll(String in, String out) { PayrollReader reader = new PayrollReader(in); PayrollWriter writer = new PayrollWriter(out); while ( reader.getNextRecord() ) { double pay = reader.hoursOf() * reader.payrateOf(); writer.printCheck(reader.nameOf(), pay); } reader.close(); writer.close(); } } ENDFIGURE===============================================================The controller need not worry about the exact format of the records in the input payroll file, and it need not worry about how paychecks are nicely printed---these responsibilities are handled by the view objects.
Figure 6 presents one possible coding of the input view, class PayrollReader.
FIGURE 6: input-view class for payroll processing======================= import java.io.*; import java.util.*; /** PayrollReader reads records from a sequential file. The records have * the format, NAME|HOURS|PAYRATE. The file is terminated by a ! */ public class PayrollReader { private BufferedReader infile; // the address of the input file private String END_OF_FILE = "!"; // the name, hours, and payrate of the most recently read record: private String name; private int hours; private double payrate; /** PayrollReader constructs the reader to read from file file_name */ public PayrollReader(String file_name) { try { infile = new BufferedReader(new FileReader(file_name)); } catch (Exception e) { System.out.println("PayrollReader error: bad file name: " + file_name + " Aborting!"); throw new RuntimeException(e.toString()); } } public String nameOf() { return name; } public int hoursOf() { return hours; } public double payrateOf() { return payrate; } public void close() { try { infile.close(); } catch (IOException e) { System.out.println("PayrollReader warning: file close failed"); } } ... ENDFIGURE======================================================
FIGURECONT 6: input-view class for payroll processing (concl.)============ /** getNextRecord attempts to read a new payroll record. * @return whether another record was read and is ready to process */ public boolean getNextRecord() { boolean result = false; name = null; hours = -1; payrate = -0.1; try { if ( infile.ready() ) { String line = infile.readLine(); StringTokenizer t = new StringTokenizer(line, "|"); String s = t.nextToken().trim(); if ( ! s.equals(END_OF_FILE) ) // finished? { if ( t.countTokens() == 2 ) // hours and payrate? { name = s; hours = new Integer (t.nextToken().trim()).intValue(); payrate = new Double (t.nextToken().trim()).doubleValue(); result = true; } else { throw new RuntimeException(line); } } } } catch (IOException e) { System.out.println("PayrollReader error: " + e.getMessage()); } catch (RuntimeException e) { System.out.println("PayrollReader error: bad record format: " + e.getMessage() + " Skipping record"); result = getNextRecord(); // try again } return result; } } ENDFIGURE=============================================================The hard work is performed within getNextRecord, which verifies the input file is nonempty before it reads the next record. The method also verifies that the record is not the end-of-file marker, !, and that the record indeed has a string name, an integer hours worked, and a double payrate. If all these conditions hold true, then the information from the record is saved, and true is returned. The method skips over badly formatted records and returns false when the end of the input is reached.
An exception handler recovers from exceptions that arise when intValue and doubleValue fail. Because we are forced to write a handler for these RuntimeExceptions, we use that handler to catch a throw new RuntimeException(line), which announces an incorrect quantity of data on an input line. Since getNextRecord must also cope with a possible IOException, we attach two exception handlers to the same try clause. These techniques with exceptions are examined in the sections that follow.
Even though it might be well written, a program can receive bad input data, for example, a sequence of letters when a number is required. When this has happened with earlier examples, the program stopped in the middle of its execution and announced that an error (exception) occurred. How can a program protect itself from this occurrence?
The Java language provides a construction, called an exception handler, that provides protection. Here is an example: A program must compute a division with an integer that is supplied as input:
import javax.swing.*; /** DivideIntoTwelve reads an int and divides it into 12 */ public class DivideIntoTwelve { public static void main(String[] args) { int i = readAnInt(); JOptionPane.showMessageDialog(null, "Answer is " + (12 / i)); System.out.println("Finished."); } /** readAnInt reads an int and returns it */ private static int readAnInt() { String s = JOptionPane.showInputDialog("Please type an int:"); int num = new Integer(s).intValue(); return num; } }
Testing reveals that this program generates an exception when the user types a 0 for the integer input; we see in the command window this message:
Exception in thread "main" java.lang.ArithmeticException: / by zero at DivideIntoTwelve.main(DivideIntoTwelve.java:0)A similar situation occurs if the user mistakenly types a non-integer, say, the text, one:
Type an int: one Exception in thread "main" java.lang.NumberFormatException: one at java.lang.Integer.parseInt(Integer.java:405) at java.lang.Integer.The message documents the methods incompleted due to the exception.(Integer.java:540) at DivideIntoTwelve.readAnIntFrom(DivideIntoTwelve.java:14) at DivideIntoTwelve.main(DivideIntoTwelve.java:6)
Both errors are examples of runtime exceptions, so named because they occur when the program is ``running.'' The wordy messages produced from exceptions are confusing to the user, and an application can do better about reporting the exceptions.
We improve the program by inserting exception handlers. An exception handler is a kind of protective ``cover'' that surrounds a questionable statement and helps the program recover from exceptions generated by the statement.
To help the program recover from division-by-zero errors, we surround the statement with the division operation with an exception handler (note the keywords, try and catch):
public static void main(String[] args) { int i = readAnInt(); try { JOptionPane.showMessageDialog(null, "Answer is " + (12 / i)); } catch(RuntimeException e) { JOptionPane.showMessageDialog(null, "Error in input: " + i + ". Restart program."); } System.out.println("Finished."); }Exception handlers are wordy---the try section brackets the questionable statement, and the catch section lists the statements that execute if an exception arises.
The statement in the try section is executed first; if i is nonzero (e.g., 2), the division proceeds without error, the dialog displaying the answer appears, and Finished. prints in the command window.
If i has value 0, however, then the division is stopped by a runtime exception---we say that an exception is thrown. At this moment, the statements in the catch section of the exception handler execute, displaying a dialog---the exception has been handled. When the exception handler completes its work, the program executes the statements that follow, as if nothing wrong had happened. (Here, Finished. prints.)
The phrase, RuntimeException e, documents the form of exception that can be caught---here, it is a runtime exception. (The purpose of the e, which is actually a formal parameter, is explained in Chapter 11.)
The general format of Java's exception handler construction goes
try { STATEMENTS } catch ( EXCEPTION ) { HANDLER }where STATEMENTS and HANDLER are sequences of zero or more statements. The EXCEPTION phrase is normally just RuntimeException e, but Chapter 11 documents other forms. When executed, the STATEMENTS part begins. If no exceptions are thrown, then the catch part is ignored. But if an exception is thrown, and if it has the form named EXCEPTION, then the statements, HANDLER, are executed.
The example in the previous section used a private method, readAnInt, to read an integer from its user. What if the user types letters instead of an integer, e.g., four instead of 4? To handle this situation, readAnInt can employ an exception handler that gives the application's user multiple tries at typing a proper integer---if the user types an improper input, the exception handler's catch-section restarts readAnInt by sending a message to the method itself:
/** readAnInt reads an int and returns it */ private static int readAnInt() { int num; String s = JOptionPane.showInputDialog("Please type an int:"); try { num = new Integer(s).intValue(); } catch(RuntimeException e) { JOptionPane.showMessageDialog(null, "Input " + s + " not an int; try again!"); num = readAnInt(); // restart with a recursive invocation! } return num; }When a non-integer is supplied as input, a runtime exception is thrown at new Integer(s), and the catch section goes to work: a dialog appears, then a message is sent to the method to restart itself, giving the user a new chance to type an integer. When a method restarts itself, it is called a recursive invocation.
Say that a user types four for the initial input; the execution configuration looks like this:
The attempted conversion of four to an integer throws a runtime exception, and execution moves into the catch section:
The restarted method asks the user for a fresh input. If the user types, say, 4, the restarted method proceeds without exception:
The restarted copy of readAnInt uses its own local variables to compute the integer, 4, that is returned to the original invocation of readAnInt:
The exception handler in the initial invocation of readAnInit finishes its repair by assigning 4 to num, which is returned to the awaiting main method.
This example shows how an exception handler can repair a truly difficult situation so that a program can proceed to its conclusion. In this and subsequent chapters, we will use recursive invocations to restart methods; Chapters 7 and 12 present other uses for recursive invocations.
Whenever we required interactive input, we wrote a sequence of statements like the following:
String input = JOptionPane.showInputDialog("Type an integer:"); int value = new Integer(input).intValue();And, if the user typed an incorrect input, the statements would throw an exception and terminate the program.
It would be better to have a standardized input-view that had skillful methods for fetching integers, doubles, and strings from the user and helping the user if an input was incorrectly typed. Once we had such an input view, we could use it as follows:
DialogReader reader = new DialogReader(); int value = reader.readInt("Type an integer:");Better still, we could reuse this same input-view, DialogReader, for all the applications we write that require input.
Table 7 states the interface we shall use for the input-view class.
TABLE 7: interface for input views===================================
This is a standardized interface for a class that reads interactive input. | |
Methods (responsibilities) | |
readString(String prompt): String | prompt the user for input with prompt, read a string, and return it as the result |
readInt(String prompt): int | prompt the user for input with prompt, read an integer, and return it as the result |
readDouble(String prompt): double | prompt the user for input with prompt, read a double, and return it as the result |
ENDTABLE=============================================================
As an example, we can change the temperature conversion application seen earlier in the text to work with the interface in Table 7. This simplifies its main method to the following:
public static void main(String[] args) { DialogReader reader = new DialogReader(); // the new input-view int c = reader.readInt("Type an integer Celsius temperature:"); // this remains unchanged: double f = celsiusIntoFahrenheit(c); DecimalFormat formatter = new DecimalFormat("0.0"); System.out.println("For Celsius degrees " + c + ","); System.out.println("Degrees Fahrenheit = " + formatter.format(f)); }
Next, we write a class that uses JOptionPane and exception handlers to match the interface. See Figure 8.
FIGURE 8: class for input via a dialog================================== import javax.swing.*; /** DialogReader accepts user input from a dialog */ public class DialogReader { /** Constructor DialogReader initializes the input view, a dialog */ public DialogReader() { } // nothing to initialize /** readString reads a string from the input source. * @param prompt - the prompt that prompts the user for input * @return the string the user types in response */ public String readString(String prompt) { return JOptionPane.showInputDialog(prompt); } /** readInt reads an integer from the input source * @param prompt - the prompt that prompts the user for input * @return the integer supplied in response */ public int readInt(String prompt) { int answer = 0; String s = readString(prompt); try { answer = new Integer(s.trim()).intValue(); } catch (RuntimeException e) { JOptionPane.showMessageDialog(null, "DialogReader error: " + s + " not an int."); answer = readInt(prompt); // restart } return answer; } /** readDouble reads a double from the input source * @param prompt - the prompt that prompts the user for input * @return the double supplied in response */ public double readDouble(String prompt) { double answer = 0; String s = readString(prompt); try { answer = new Double(s.trim()).doubleValue(); } catch (RuntimeException e) { JOptionPane.showMessageDialog(null, "DialogReader error: " + s + " not a double."); answer = readDouble(prompt); // restart } return answer; } } ENDFIGURE==============================================================
Method readString reads a line of text from the dialog, using the standard technique.
The readInt method uses readString to fetch the user's input, which must be converted from a string, s, into an integer in the standard way:
answer = new Integer(s.trim()).intValue();There is a small novelty: The string method, trim(), removes the leading and trailing blanks from the string before it is given to the Integer helper object for conversion. (Recall from Chapter 3 that string operations are written in the format, S.op(args), for a string, S, operation, op, and arguments (if any), args.) For example, if readString(prompt) returned the string, " 123 ", then " 123 ".trim() computes to "123".
If the user typed a non-integer, then the attempt to convert it into an integer would throw a runtime exception. Fortunately, the exception handler handles the exception by restarting the readInt method, just like we saw in the previous section.
Method readDouble works similarly to readInt.
We have seen how exceptions can terminate a program's execution and how an exception handler can trap an exception so that execution can resume. Now, we learn that exceptions are objects that bind to the formal parameters of of exception handlers.
When an error (such as division by zero) occurs, the program must collect information about the error and deliver the information to a handler of the exception. The information collected about the error usually comes in two parts:
There are many classes from which exception objects can be built, e.g., class ArrayIndexOutOfBoundsException and class NullPointerException are two examples; both are found in the java.lang package. Figure 9 gives a partial listing of the exception hierarchy; no doubt, many of its class names are familiar.
FIGURE 9: classes of exceptions========================================== Object | +-Throwable: getMessage():String, toString():String, printStackTrace() | +-Exception | +-InterruptedException | +-RuntimeException | | | +-ArithmeticException | | | +-ArrayIndexOutOfBoundsException | | | +-ClassCastException | | | +-NullPointerException | | | +-NumberFormatException | | | +-StringIndexOutOfBoundsException | | | +-NoSuchElementException | +-IOException | +-FileNotFoundException | +-EOFException | +-InterruptedIOException ENDFIGURE==============================================================
Exception objects have several useful methods, presented in Table 10
TABLE 10: methods for exception objects================================
getMessage(): String | Return the message that describes the error that occurred |
toString(): String | Return a string representation of the exception object; this is usually the class name of the exception and the message that describes the error. |
printStackTrace() | Print on the console window the statement number, method, and class where the error occurred as well as all the method invocations that led to it. |
ENDTABLE==============================================================The methods extract the information embedded within an exception. We use them momentarily.
As noted in Figure 9, the exceptions we have encountered fall into two groups: runtime exceptions and input-output exceptions. Runtime exceptions are subtle errors that are a fact of programming life. Since they can occur at almost every statement, the Java compiler assumes that every method in a program ``throws RuntimeException.'' In contrast, input-output exceptions are errors that might arise due to a serious problem with file usage. Such an error is uncommon, and the Java compiler requires that every method which might generate the error be labelled with throws IOException.
The usual outcome of an error is the creation of an exception object that finds its way to its default handler, the IDE or JDK. But in some cases, a programmer might prefer that the exception object find its way to a different handler, one that does not terminate execution---the programmer writes an exception handler to ``catch'' and ``handle'' exception objects.
The format of an exception handler goes
try { STATEMENTS } catch (EXCEPTION_TYPE e) { HANDLER }It defines a method, catch, that is ``invoked'' within STATEMENTS if an exception occurs there. If an exception object is created within STATEMENTS, and if the exception's type is a subtype of EXCEPTION_TYPE, then the exception object binds to formal parameter e, and HANDLER executes. (If the exception's type is not a subtype of EXCEPTION_CLASS, then the exception ``escapes'' to find its handler.) Once HANDLER finishes, execution continues with the statements that follow it. Within HANDLER, messages can be sent to the exception object, e.g., e.printStackTrace().
If a programmer writes an exception handler, she is obligated to make the handler repair the error to a degree that execution can safely proceed. In this regard, programmer-written exception handlers can prove dangerous, because it is all too easy for a programmer to do too little to repair an error.
Here is a simple example. Perhaps we must write a method, readAnIntFrom, which reads a string from the input-view object and converts it into a string. An exception handler is added to cope with the possibility that the input string is not an integer:
/** readAnIntFrom reads an int from input-view, view, and returns it */ private int readAnIntFrom(BufferedReader view) throws IOException { int num; System.out.print("Type an int: "); String s = view.readLine(); try { num = new Integer(s).intValue(); } catch (RuntimeException e) { System.out.println("Non-integer error"); num = -1; } return num; }If the string returned by readLine is nonnumeric, then a runtime exception arises at new Integer(s).intValue(). A NumberFormatException object is created and is promptly caught by the enclosing catch, which merely prints a message and lets the method return -1 as its result. Since -1 might be confused for a legitimate integer input, the exception handler has not done a good enough job. We improve it as seen in Figure 11.
FIGURE 11: improved exception handling for reading integers=============== /** readAnIntFrom reads an integer from the input source * @param view - the input-view object * @return the integer supplied in response. (And keep trying until the user * supplies a legal integer!) */ private int readAnIntFrom(BufferedReader view) throws IOException { int num; System.out.print("Type an int: "); String s = view.readLine(); try { num = new Integer(s).intValue(); } catch(NumberFormatException e) { JOptionPane.showMessageDialog(null, "Error: " + e.getMessage() + " not an integer; try again."); num = readAnIntFrom(view); // restart } return num; } ENDFIGURE============================================================
In the Figure, the handler insists that the user type another string, and it invokes itself to restart the process. This ensures that the method concludes only when a proper integer has been typed. The message, e.getMessage(), extracts information about the nature of the error. Finally, the exception handler is refined to handle only NumberFormatExceptions, which ensures that the handler is used only for the form of error it is prepared to handle, namely, a format error for a string that is nonnumeric.
When an exception is thrown, it is not always obvious which catch-construct will handle it. If an exception is thrown within a method, and if the method does not catch the exception, then the exception seeks its handler by returning to the method that invoked the erroneous method and searching there.
The following artificial example explores this idea. Perhaps we have
ExceptionExample ex = new ExceptionExample(); ex.f();where class ExceptionExample is defined as
import java.io.*; public class ExceptionExample { public ExceptionExample() { } public void f() { try { g(); } catch (RuntimeException e) { System.out.println("caught at f"); } System.out.println("f completes"); } public void g() { try { PrintWriter outfile = new PrintWriter(new FileWriter("text.out")); try { outfile.println( h() ); } catch (NullPointerException e) {System.out.println("null pointer caught at g"); } } catch (IOException e) { System.out.println("io error caught at g"); } System.out.println("g completes"); } private int h() { int[] r = new int[2]; return r[3]; } }The message to f causes a message to g which causes a message to h, where an array indexing error occurs. Within h, an ArrayIndexOutOfBoundsException object is created, and the plan of returning an integer from h is abandoned. Instead, the exception object seeks its handler. No handler appears in h, so the exception returns to the position where h was invoked. This places it at the statement, outfile.println( h() ) within g. The plan to perform the outfile.println is abandoned; the exception seeks its handler instead. It examines the data type, NullPointerException, of the nearest enclosing handler, but the type does is not a subtype of ArrayIndexOutOfBoundsException, so the exception object searches further. The next enclosing handler has type IOException, but this is also unacceptable, so the exception returns from g to the position, g(), within method f.
Here, the exception finds a handler that can accommodate it: The exception's type is a subtype of RuntimeException, so the message, caught at f, is printed. The handler consumes the exception, and normal execution resumes at the statement, System.out.println("f completes"), which prints. Execution concludes at ex.f(), which is unaware of the problems that occurred.
The above example was meant to illustrate a technical point; it is not an example of good programming style. Indeed, exception handlers are best used to help a program recover from actions over which it has no influence, e.g., a user typing invalid input. Exception handlers are not so useful for detecting internal programming errors such as array indexing errors. As a rule,
int i = reader.readInt("Please type an array index:"); if ( i > 0 && i < r.length ) // prevent an error { System.out.println(r[i]); } else { System.out.println("invalid index: " + i); }is always preferred to
int i = reader.readInt("Please type an array index:"); try { System.out.println(r[i]); } catch (RuntimeException e) // recover from an error { System.out.println(e.toString()); }
import java.io.*; public class Squares { public static void main(String[] args) throws IOException { BufferedReader infile = new BufferedReader(new FileReader("ints.dat")); while ( infile.ready() ) { int i = new Integer(infile.readLine().trim()).intValue(); System.out.println(i + " squared is " + (i*i)); } infile.close(); } }Revise this class so that an invalid input line causes the program to display Illegal input---skipping to next line and causes the program to proceed to the next line of input.
In the rare case, it is possible for a programmer to escape from a disastrous situation by constructing an exception object:
try { ... // perhaps some complicated computation is undertaken if ( some_bad_condition_is_true ) { throw new RuntimeException("terrible error"); } ... // otherwise, continue with the complicated computation } catch (RuntimeException e) { ... e.getMessage() ... } // try to handle the bad conditionThe statement, throw new RuntimeException(S), constructs a runtime exception object and ``throws'' to its handler. String S is the message embedded within the exception and is the answer produced by a getMessage.
If one wants to be certain that the explicitly generated exception is not inadvertantly confused with the other forms of runtime exceptions, one can create a new class for the new exception, e.g.,
public class ComplicatedException extends Exception { private int villain; // the integer that caused all the trouble public ComplicatedException(String error_message, int the_bad_number) { super(error_message); villain = the_bad_number; } public int getVillain() { return villain; } }Here, class ComplicatedException is a subclass of Exception, meaning that its objects can be ``thrown.'' Its constructor method has been enhanced to accept both an error message and an integer, which might be useful to its exception handler. The new class of exception might be used as follows:
try { int x = ... ; ... // some complicated computation is undertaken if ( some_bad_condition_is_true_about(x) ) { throw new ComplicatedException("terrible error", x); } ... // otherwise, continue with the complicated computation } catch (ComplicatedException e) { ... e.getMessage() ... int i = e.getVillain(); ... }The statement, throw new ComplicatedException("terrible error", x), builds a ComplicatedException object and throws it to its handler, which sends messages to the exception to learn the nature of the error.
public int readInt(String prompt) { int answer = 0; String s = readString(prompt); try { answer = new Integer(s.trim()).intValue(); } catch (RuntimeException e) { JOptionPane.showMessageDialog(null, "DialogReader error: " + s + " not an int."); answer = readInt(prompt); // restart } return answer; }
PrintWriter outfile = new PrintWriter(new FileWriter("test.txt"));One uses the print and println methods of the object to write text to the file.
An input file is easily created from a BufferedReader object, e.g.,
BufferedReader infile = new BufferedReader(new FileReader("test.txt"));One uses the readLine method to read one line of text from the file.
Exceptions are in fact objects, and a programmer can write an exception handler, try ... catch, to ``catch'' and ``handle'' an exception.
Mean = ( n_0 + n_1 + ... n_m ) / m _______________________________________________ StdDev = \/ -(Mean^2) + (n_0)^2 + (n_1)^2 + ... + (n_m)^2where the input file contains the m integers, n_0, n_1, ..., n_m. (Recall the standard deviation tells us that two-thirds of the integers in the file fall in the range (Mean - StdDev) .. (Mean + StdDev).)
Frequency of word occurrence: ------------------------------ 1: **** 2: ******* 3: ********* 4: **** 5: ****** 6: ** ...
Thisssss is aaaa testtt 22225.is compressed to
Thi\5s is \4a\6 testtt \425.(Assume that \ does not appear in the input file.)
FIGURE 12: copying a file character by character========================= import java.io.*; /** CharCopy copies in.dat to out.dat character by character */ public class CharCopy { public static void main(String[] args) throws IOException { FileReader infile = new FileReader("in.dat"); FileWriter outfile = new FileWriter("out.dat"); while ( infile.ready() ) { int c = infile.read(); // reads (the coding of) a character outfile.write(c); // writes the character } infile.close(); outfile.close(); } } ENDFIGURE==============================================================
For reasons explained at the end of the chapter, it suffices to work with a FileReader object to read character input and a FileWriter object to write character output. The statement, int c = infile.read() reads a character from the input file, but the character is returned as an integer. (This hides the differences between the one-byte ASCII codings of characters used by some computers and the two-byte Unicode codings used by others; both codings are accommodated by using an integer.) The write method copies the character to the output file.
If one wishes to work with the character that is read, it must be cast into type char, to inform the Java compiler that it is indeed a character:
int c = infile.read(); char d = (char)c; String s = "abc" + d;Conversely, if one tries to write an arbitrary integer, e.g., outfile.write(-9999), the write method sends an ``error character'' to the output file.
double d = 1000007.5; System.out.println(d);A binary representation of one-million-seven-and-a-half is saved in a single cell, and this representation is fetched and coverted into a sequence of nine characters for display on the console window. Conversions of numbers to characters are time consuming, and it is best to avoid them.
If a huge array of numbers must be saved in a file for later computation, it is best to use a binary file. Java provides class ObjectOutputStream and class ObjectInputStream for writing and reading binary files of numbers, booleans, and arbitrary objects. The values are copied in their binary, storage representations; there is no conversion to character format.
For example, to write the above number to a binary file, data.out, we create an output-file object and send a writeDouble message to it:
double d = 1000007.5; ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream("data.out")); out.writeDouble(d);Similarly, to read a complete file, data.in, of doubles, we would state
ObjectInputStream in = new ObjectInputStream (new FileInputStream("data.in")); while ( in.available() > 0 ) // more data left to read? { double d = in.readDouble(); ... } in.close();In a similar way, one can read and write integers and booleans.
As the name suggests, entire objects can be written to an ObjectOutputStream and read from an ObjectInputStream. Figure 12 displays a program that writes an integer and several objects to a binary file and then reads the file and restores the objects.
FIGURE 12: writing and reading objects from a file======================= import java.io.*; /** ObjectFileTest writes and reads two objects and an integer */ public class ObjectFileTest { public static void main(String[] args) throws IOException, ClassNotFoundException { Cell b = new Cell(5); ComposedCell c = new ComposedCell(b); Cell d = new Cell(7); ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream("test.ob")); out.writeObject(c); out.writeInt(49); out.writeObject(d); out.close(); // now, read the objects and the integer and restore them: ObjectInputStream in = new ObjectInputStream (new FileInputStream("test.ob")); Object ob = in.readObject(); ComposedCell m = (ComposedCell)ob; // cast ob into a ComposedCell System.out.println(m.answerOf()); System.out.println(in.readInt()); ob = in.readObject(); Cell n = (Cell)ob; // cast ob into a Cell System.out.println(n.valueOf()); in.close(); } } /** ComposedCell builds an object that holds within it two objects */ public class ComposedCell implements Serializable { private Cell x; private Cell y; public ComposedCell(Cell my_cell) { x = my_cell; y = new Cell(0); } public int answerOf() { return x.valueOf() + y.valueOf(); } } ENDFIGURE==========================================================
FIGURECONT 12: writing and reading objects from a file (concl.)=========== /** Cell holds an integer */ public class Cell implements Serializable { private int value; public Cell(int start) { value = start; } public int valueOf() { return value; } } ENDFIGURE============================================================
Objects are written to files with the writeObject method. Of course, objects often have fields that hold addresses of other objects, as is the case with ComposedCell c in the Figure. So, writeObject(c) saves not only c's object but also the objects whose addresses are held by c's fields, x and y. An integer and a Cell object are also written, to show that different types of values can be mixed in the binary file.
When the file test.ob is reopened as an input file, the file's contents must be read in the same order in which it was written. This means the ComposedCell object must be read first:
Object ob = in.readObject(); ComposedCell m = (ComposedCell)ob; // cast ob into a ComposedCellThe readObject method returns a result of type object; this object must be cast into type ComposedCell, and at this point, the object is restored. The integer and Cell object are read next.
Both classes ComposedCell and Cell are labelled implements Serializable to assert that objects created from the classes can be saved in sequential binary files. (The Java compiler verifies that a ``serializable'' class contains only fields that can be safely copied onto files.) Also, a method that reads a binary file of objects must state throws ClassNotFoundException to warn of the possibility that an object of unknown data type (class) might be read.
The java.io package holds a variety of classes for input and output; Figure 13 lists those classes used in this text.
FIGURE 13: class hierarchy for input-output============================= Object | +-File: File(String), getPath():String, isFile():boolean, isDirectory():Boolean | +-InputStream (abstract): available():int, close() | | | +-FileInputStream: FileInputStream(String), FileInputStream(File) | | read():int | | | +-FilterInputStream: read():int | | | +-ObjectInputStream: ObjectInputStream(InputStream), read():int, | readInt():int, readDouble():double, readBoolean():boolean, | readObject():Object | +-OutputStream (abstract): close() | | | +-FileOutputStream: FileOutputStream(String), FileOutputStream(File), | | write(int) | | | +-FilterOutputStream: write(int) | | | +-ObjectOutputStream: ObjectOutputStream(OutputStream), write(int), | writeInt(int), writeDouble(double), writeBoolean(boolean), | writeObject(Object) | +-Reader (abstract) | | | +-InputStreamReader: InputStreamReader(InputStream), read():int, | | | ready():boolean, close() | | | | | +-FileReader: FileReader(String) | | | +-BufferedReader: BufferedReader(Reader), read():int, ready():boolean, | readLine():String, close() | +-Writer (abstract) | +-OutputStreamWriter: OutputStreamWriter(OutputStream), write(int), close() | | | +-FileWriter: FileWriter(String) | +-PrintWriter: PrintWriter(Writer), PrintWriter(OutputStream), write(int), print(int), print(double), print(boolean), print(Object), println(int), println(double), println(boolean), println(Object), println(), close() ENDFIGURE===============================================================Because of a history of alterations to the package, the intentions behind the class hierarchy are less clear than they might be. When studying the hierarchy, keep these ideas in mind:
Here are some examples of objects for input and output. To assemble an object that reads strings (lines of characters) from a file, data.in, we might say
BufferedReader infile = new BufferedReader (new InputStreamReader(new FileInputStream("data.in"));First, new FileInputStream("data.in") locates data.in and creates an input stream whose read method reads one byte at a time. Next, new InputStreamReader(...) creates a character stream whose read method uses FileInputStream's read to assemble one character at a time. Finally, new BufferedReader(...) creates a buffered reader whose readLine method uses InputStreamReader's read to assemble one full line of characters and return the line as a string.
As a convenience, the composed object, new InputStreamReader(new FileInputStream("data.in")), can be created in one step with new FileReader("data.in"):
BufferedReader infile = new BufferedReader(new FileReader("data.in"));and this is the pattern used in this chapter to build most input-file objects. Also, the declaration,
FileReader in = new FileReader("data.in");creates a reader from which we can read characters one at a time. (But the reader is not buffered.)
In a similar way,
PrintWriter outfile = new PrintWriter(new FileWriter("test.txt"));creates a buffered writer that uses a writer, new FileWriter("file"). The latter abbreviates the character output stream, new OutputStreamReader(new FileOutputStream("test.txt")).
We create a file to which objects are written with this declaration:
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("d.ob"));This builds an object stream whose writeObject method uses FileOutputStream's write to write objects as sequences of bytes to the file, d.ob.
The tables that follow provide details for the classes in Figure 13.
class File | contains the path name and basic properties of a file |
Constructor | |
File(String path) | Constructs the file object from the complete path name, path. |
Methods | |
getPath(): String | Return the complete directory path name of the file object. |
isFile(): boolean | Return whether the file object is a file. |
isDirectory(): boolean | Return whether the file object is a directory. |
abstract class InputStream | a sequence (``stream'') of data items that can be read one byte at a time |
Methods | |
available(): int | Return how many bytes are available for reading. |
close() | Close the file. |
class FileInputStream extends InputStream | a stream that is a sequential disk file |
Constructors | |
FileInputStream(String path) | Open the disk file whose path name is path. |
FileInputStream(File f) | Open the disk file named by f. |
Method | |
read(): int | Read the next unread byte from the file and return it; return -1 if the file is at the end. |
class FilterInputStream extends InputStream | a basic input stream, e.g., from the keyboard. |
Method | |
read(): int | Return the next byte from the stream; return -1 if the end of the stream is reached. |
class ObjectInputStream extends InputStream | an input stream from which binary values (primitive values and objects) can be read |
Constructor | |
ObjectInputStream(InputStream stream) | Uses stream to construct an object-input stream |
Methods | |
read(): int | Return the next byte from the stream; return -1 if the end of the stream is reached. |
readInt(): int | Return a 32-bit integer assembled from bytes read from the stream; throws EOFException if end of the stream is reached. |
readDouble(): double, readBoolean(): boolean | Similar to readInt(). |
readObject(): Object | Return an object assembled from bytes read from the stream; throws an exception if a complete object cannot be assembled. |
abstract class OutputStream | a sequence (``stream'') of data items that have been written |
Method | |
close() | Close the file. |
class FileOutputStream | an output stream that is a sequential disk file |
Constructors | |
FileOutputStream(String path) | Open the disk file whose path name is path. |
FileOutputStream(File f) | Open the disk file named by f. |
Methods | |
write(int b) | Write byte b to the file. |
close() | Close the file. |
class FilterOutputStream extends OutputStream | a basic output stream, e.g., to the console window. |
Method | |
write(int b) | Write byte b to the output stream. |
class ObjectOutputStream extends OutputStream | an output stream to which binary values (primitive values and objects) can be written |
Constructor | |
ObjectOutputStream(OutputStream stream) | Uses stream to construct an object-output stream |
Methods | |
write(int b) | Write byte b to the stream. |
writeInt(int i) | Writes i, a 32-bit integer, to the stream. |
writeDouble(double d), writeBoolean(boolean b) | Similar to writeInt(). |
writeObject(Object ob) | Writes ob to the output stream, provided that the class from which ob was constructed implements Serializable; throws an exception, otherwise. |
abstract class Reader | a sequence of characters that can be read |
class InputStreamReader extends Reader | a basic character stream |
Constructor | |
InputStreamReader(InputStream stream) | Construct a character stream that reads bytes from stream to assemble its characters. |
Methods | |
read(): int | Read the next unread character from the stream and return it as an integer; return -1 if the end of the stream is reached. |
ready(): boolean | Return whether the stream contains more characters to be read. |
close() | Close the stream. |
class FileReader extends Reader | an InputStreamReader constructed from a FileInputStream. |
Constructors | |
FileReader(String s), FileReader(File s), | Constructs the input-stream reader---an abbreviation for new InputStreamReader(new FileInputStream(s)). |
class BufferedReader extends Reader | a character stream from which complete lines of text can be read |
Constructor | |
BufferedReader(Reader r) | Construct a buffered reader that reads its characters from r. |
Methods | |
read(): int | Read the next unread character from the stream and return it as an integer; return -1 if the end of the stream is reached. |
readLine(): String | Read a line of text from the stream, that is, a maximal sequence of characters terminated by a newline ('\n') or return-newline ('\r' and '\n'). Return the line less its terminator characters; return null if the end of the stream is reached. |
ready(): boolean | Return whether the stream contains more characters to be read. |
close() | Close the stream. |
abstract class Writer | a writable sequence of characters |
class OutputStreamWriter extends Writer | a basic character stream |
Constructor | |
OutputStreamWriter(OutputStream stream) | Construct a character stream to which characters can be written by means of stream. |
Methods | |
write(int c) | Write character c to the stream. |
close() | Close the stream. |
class FileWriter extends Writer | an OutputStreamWriter constructed from a FileOutputStream |
Constructors | |
FileWriter(String s), FileWriter(File s), | Constructs the output-stream writer---an abbreviation for new OutputStreamWriter(new FileOutputStream(s)). |
class PrintWriter extends Writer | a buffered character stream to which primitive values and objects can be written (in their string depictions) |
Constructors | |
PrintWriter(OutputStream s), PrintWriter(Writer s) | Construct a print writer that sends its values as character sequences to s. |
Methods | |
write(int c) | Write character c to the output stream. |
print(int d), print(double d), print(boolean d), print(Object d) | Write the character representation of d to the output stream. (When d is an object, d's toString method is used to get its representation.) |
println(int d), println(double d), println(boolean d), println(Object d) println() | Like print but appends line termination character(s). |
close() | Close the stream. |
The file chooser lets a user click on a folder to open it and click on a file name to select a file. When the user presses the Select button, the path name of the selected file is remembered, and the program that created the file chooser can retrieve the path name.
A simple way to create and show a file chooser is
JFileChooser chooser = new JFileChooser(); chooser.setDialogTitle("Choose a file"); int result = chooser.showDialog(null, "Select");The resulting dialog displays the contents of the folder (directory) of the user's ``home'' folder; the dialog's title is set by setDialogTitle. After the user selects a file and presses the button labelled Select, the selected file's path name is retrieved and used as follows:
if ( result == JFileChooser.APPROVE_OPTION ) { File f = chooser.getSelectedFile(); String path_name = f.getPath(); ... // use path_name to create a file object } else { System.out.println ("Error: Select button was not pushed or pushed with no file selected"); }The file chooser returns an integer code which equals JFileChooser.APPROVE_OPTION if the user selected a file (or typed a file name into the dialog's text area) and terminated the dialog by pushing the Select button (or pressing Enter in the text area). The getSelectedFile method returns a File object from which one extracts the path name with getPath.
Figure 14 shows some of the methods for class JFileChooser.
TABLE 14: methods for JFileChooser================================
class JFileChooser | |
Constructors | |
JFileChooser() | Constructs a file chooser which points to the contents of the user's home folder (directory) |
JFileChooser(String path_name) | Constructs a file chooser which points to the contents of the folder path_name. |
JFileChooser(File path_name) | Constructs a file chooser which points to the contents of the folder path_name. |
Methods | |
getSelectedFile(): File | Return the file selected in the file chooser's view |
setDialogTitle(String title) | Set the title of the file chooser's dialog to title |
showDialog(Component owner, String text_for_approve_button) | Display the file chooser as a dialog, using owner as the dialog's owner component. The approval button is labelled with text_for_approve_button. |
ENDTABLE=============================================================