CHAPTER GOALS
To be able to read and write text files
To learn how to throw and catch exceptions
To be able to design your own exception classes
To understand the difference between checked and unchecked exceptions
To know when and where to catch an exception
This chapter starts with a discussion of file input and output. Whenever you read or write data, potential errors are to be expected. A file may have been corrupted or deleted, or it may be stored on another computer that was just disconnected from the network. In order to deal with these issues, you need to know about exception handling. This chapter tells you how your programs can report exceptional conditions, and how they can recover when an exceptional condition has occurred.
We begin this chapter by discussing the common task of reading and writing files that contain text. Examples are files that are created with a simple text editor, such as Windows Notepad, as well as Java source code and HTML files.
The simplest mechanism for reading text is to use the Scanner
class. You already know how to use a Scanner
for reading console input. To read input from a disk file, the Scanner
class relies on another class, File
, which describes disk files and directories. (The File
class has many methods that we do not discuss in this book; for example, methods that delete or rename a file.) First construct a File
object with the name of the input file, then use the File
to construct a Scanner
object:
File inFile = new File("input.txt"); Scanner in = new Scanner(inFile);
This Scanner
object reads text from the file input.txt
. You can use the Scanner
methods (such as next, nextLine, nextInt
, and nextDouble
) to read data from the input file.
To write output to a file, you construct a PrintWriter
object with the given file name, for example
PrintWriter out = new PrintWriter("output.txt");
If the output file already exists, it is emptied before the new data are written into it. If the file doesn't exist, an empty file is created. You can also construct a PrintWriter
object from a File
object. This is useful if you use a file chooser (see Special Topic 11.1).
The PrintWriter
class is an enhancement of the PrintStream
class that you already know—System.out
is a PrintStream
object. You can use the familiar print, println
, and printf
methods with any PrintWriter
object:
out.print(29.95); out.println(new Rectangle(5, 10, 15, 25)); out.printf("%10.2f", price);
When you are done writing to a file, be sure to close the PrintWriter
:
out.close();
You must close a print stream when you are done writing output.
If your program exits without closing the PrintWriter
, the disk file may not contain all of the output.
The following program puts these concepts to work. It reads all lines of an input file and sends them to the output file, preceded by line numbers. If the input file is
Mary had a little lamb Whose fleece was white as snow. And everywhere that Mary went, The lamb was sure to go!
then the program produces the output file
/* 1 */ Mary had a little lamb /* 2 */ Whose fleece was white as snow. /* 3 */ And everywhere that Mary went, /* 4 */ The lamb was sure to go!
The line numbers are enclosed in /* */
delimiters so that the program can be used for numbering Java source files.
There is one additional issue that we need to tackle. When the input or output file doesn't exist, a FileNotFoundException
can occur. The compiler insists that we tell it what the program should do when that happens. (In this regard, the FileNotFoundException
is different from the exceptions that you have already encountered. We will discuss this difference in detail in Section 11.4.) In our sample program, we take the easy way out and acknowledge that the main
method should simply be terminated if the exception occurs. We label the main
method like this:
public static void main(String[] args) throws FileNotFoundException
You will see in the following sections how to deal with exceptions in a more professional way.
ch11/lines/LineNumberer.java
1
import java.io.File;2
import java.io.FileNotFoundException;3
import java.io.PrintWriter;4
import java.util.Scanner;5
6
/**7
This program applies line numbers to a file.8
*/9
public class LineNumberer10
{11
public static void main(String[] args) throws FileNotFoundException12
{13
// Prompt for the input and output file names14
15
Scanner console = new Scanner(System.in);16
System.out.print("Input file: ");17
String inputFileName = console.next();18
System.out.print("Output file: ");19
String outputFileName = console.next();20
21
// Construct the Scanner and PrintWriter objects for reading and writing22
23
File inputFile = new File(inputFileName);24
Scanner in = new Scanner(inputFile);25
PrintWriter out = new PrintWriter(outputFileName);26
int lineNumber = 1;27
28
// Read the input and write the output29
30
while (in.hasNextLine())31
{32
String line = in.nextLine();33
out.println("/* " + lineNumber + " */ " + line);34
lineNumber++;35
}36
37
in.close();38
out.close();39
}40
}
2. What happens when you supply the name of a nonexistent input file to the Line-Numberer
program?
In the following sections, you will learn how to process complex text input that you often encounter in real life situations.
In the preceding example program, we read input a line at a time. Sometimes, it is useful to read words rather than lines. For example, consider the loop
while (in.hasNext()) { String input = in.next(); System.out.println(input); }
The next
method reads a word at a time. Call Scanner. useDelimiter
to specify a pattern for word boundaries.
With our sample input, this loop would print a word on every line:
Mary had a little lamb
In Java, a word is not the same as in English. It is any sequence of characters that is not white space. White space includes spaces, tab characters, and the newline characters that separate lines. For example, the following are considered words:
snow. 1729 C++
(Note the period after snow
—it is considered a part of the word because it is not white space.)
Here is precisely what happens when the next method is executed. Input characters that are white space are consumed—that is, removed from the input. However, they do not become part of the word. The first character that is not white space becomes the first character of the word. More characters are added until either another white space character occurs, or the end of the input has been reached.
Sometimes, you want to read just the words and discard anything that isn't a letter. You achieve this task by calling the useDelimiter
method on your Scanner
object as follows:
Scanner in = new Scanner(...); in.useDelimiter("[^A-Za-z]+");
Here, we set the character pattern that separates words to "any sequence of characters other than letters". (The notation used for describing the character pattern is called a regular expression. See Productivity Hint 11.1 on page 477 if you are interested in more details.) With this setting, punctuation and numbers are stripped off from the words returned by the next
method.
When each line of a file is a data record, it is often best to read entire lines with the nextLine
method:
String line = in.nextLine();
The nextLine
method consumes the next input line (including the newline character) and returns the line without the newline character. You can then take the line apart for further processing.
The nextLine
method reads a line of input and consumes the newline character at the end of the line.
Here is a typical example of processing lines in a file. A file with population data from the CIA Fact Book site (http://www.cia.gov/library/publications/the-world-factbook/
) contains lines such as the following:
China 1330044605 India 1147995898 United States 303824646 ...
Because some country names have more than one word, it would be tedious to read this file using the next
method. For example, after reading United
, how would your program know that it still needs to read another word before reading the population count?
Instead, read each input line into a string. Then use the isDigit
and isWhitespace
methods to find out where the name ends and the number starts.
Locate the first digit:
int i = 0; while (!Character.isDigit(line.charAt(i))) { i++; }
Then extract the country name and population:
String countryName = line.substring(0, i); String population = line.substring(i);
However, the country name contains one or more spaces at the end. Use the trim
method to remove them:
countryName = countryName.trim();
The trim
method returns the string with all white space at the beginning and end removed.
There is another problem. The population is stored in a string, not a number. Use the Integer.parseInt
method to convert it:
int populationValue = Integer.parseInt(population);
You need to be careful when calling the Integer.parseInt
method. Its parameter value must be a string containing the digits of an integer or a NumberFormatException
occurs. The parameter value may not contain any additional characters. Not even spaces are allowed! In our situation, we happen to know that there won't be any spaces at the beginning of the string, but there might be some at the end. Therefore, we use the trim
method:
int populationValue = Integer.parseInt(population.trim());
Here you saw how to break a string into parts by looking at individual characters. Another approach is occasionally easier. Construct a new Scanner
object to read the characters from a string:
Scanner lineScanner = new Scanner(line);
Then you can use lineScanner
like any other Scanner
object, reading words and numbers:
String countryName = lineScanner.next(); while (!lineScanner.hasNextInt()) { countryName = countryName + " " + lineScanner.next(); } int populationValue = lineScanner.nextInt();
You have used the nextInt
and nextDouble
methods of the Scanner
class many times, but here we will have a look at their behavior in detail. Suppose you call
double value = in.nextDouble();
The nextInt
and nextDouble
methods consume white space and the next number.
The nextDouble
method recognizes floating-point numbers such as 3.14159, −21
, or 1E12
(a billion in scientific notation). However, if there is no number in the input, then a NoSuchElementException
occurs.
Consider an input containing the characters
White space is consumed and the word 21st
is read. However, this word is not a properly formatted number. In this situation, an "input mismatch exception" occurs.
To avoid exceptions, use the hasNextDouble
method to screen the input. For example,
if (in.hasNextDouble()) { double value = in.nextDouble(); ... }
Similarly, you should call the hasNextInt
method before calling nextInt
.
Note that the nextInt
and nextDouble
methods do not consume the white space that follows a number. This can be a problem if you alternate between calling nextInt
/nextDouble
and nextLine
. Suppose a file contains student IDs and names in this format:
1729 Harry Morgan 1730 Diana Lin ...
Now suppose you read the file with these instructions:
while (in.hasNextInt()) { int studentID = in.nextInt(); String name = in.nextLine(); Process the student ID and name }
Initially, the input contains
After the first call to nextInt
, the input contains
The call to nextLine
reads an empty string! The remedy is to add a call to nextLine
after reading the ID:
int studentID = in.nextInt(); in.nextLine(); // Consume the newline String name = in.nextLine();
Sometimes, you want to read a file one character at a time. You achieve this task by calling the useDelimiter
method on your Scanner
object with an empty string:
Scanner in = new Scanner(...); in.useDelimiter("");
To read one character at a time, set the delimiter pattern to the empty string.
Now each call to next
returns a string consisting of a single character. Here is how you can process the characters:
while (in.hasNext()) {
char ch = in.next().charAt(0); Process ch }
int number = in.nextInt(); String input = in.next();
4. Suppose the input contains the characters 6,995.00 12
. What is the value of price
and quantity
after these statements?
double price = in.nextDouble(); int quantity = in.nextInt();
5. Your input file contains a sequence of numbers, but sometimes a value is not available and marked as N/A
. How can you read the numbers and skip over the markers?
There are two main aspects to exception handling: reporting and recovery. A major challenge of error handling is that the point of reporting is usually far apart from the point of recovery. For example, the get
method of the ArrayList
class may detect that a nonexistent element is being accessed, but it does not have enough information to decide what to do about this failure. Should the user be asked to try a different operation? Should the program be aborted after saving the user's work? These decisions must be made in a different part of the program.
In Java, exception handling provides a flexible mechanism for passing control from the point of error reporting to a competent recovery handler. In the remainder of this chapter, we will look into the details of this mechanism.
When you detect an error condition, your job is really easy. You just throw
an appropriate exception object, and you are done. For example, suppose someone tries to withdraw too much money from a bank account.
public class BankAccount { ... public void withdraw(double amount) { if (amount > balance) // Now what? ... } }
To signal an exceptional condition, use the throw
statement to throw an exception object.
First look for an appropriate exception class. The Java library provides many classes to signal all sorts of exceptional conditions. Figure 1 on the next page shows the most useful ones.
Look around for an exception type that might describe your situation. How about the IllegalStateException
? Is the bank account in an illegal state for the withdraw
operation? Not really—some withdraw
operations could succeed. Is the parameter value illegal? Indeed it is. It is just too large. Therefore, let's throw an IllegalArgumentException
. (The term argument is an alternative term for a parameter value.)
public class BankAccount { public void withdraw(double amount) { if (amount > balance) { throw new IllegalArgumentException("Amount exceeds balance"); } balance = balance - amount; } ... }
The statement
throw new IllegalArgumentException("Amount exceeds balance");
constructs an object of type IllegalArgumentException
and throws that object.
When you throw an exception, the current method terminates immediately.
When you throw an exception, execution does not continue with the next statement but with an exception handler. For now, we won't worry about the handling of the exception. That is the topic of Section 11.5.
7. Suppose you construct a new bank account object with a zero balance and then call withdraw(10)
. What is the value of balance
afterwards?
Java exceptions fall into two categories, called checked and unchecked exceptions. When you call a method that throws a checked exception, the compiler checks that you don't ignore it. You must tell the compiler what you are going to do about the exception if it is ever thrown. For example, all subclasses of IOException
are checked exceptions. On the other hand, the compiler does not require you to keep track of unchecked exceptions. Exceptions such as NumberFormatException, IllegalArgument-Exception
, and NullPointerException
are unchecked exceptions. More generally, all exceptions that belong to subclasses of RuntimeException
are unchecked, and all other subclasses of the class Exception
are checked. (In Figure 1, the checked exceptions are shaded in a darker color.) There is a second category of internal errors that are reported by throwing objects of type Error
. One example is the OutOfMemoryError
, which is thrown when all available memory has been used up. These are fatal errors that happen rarely and are beyond your control. They too are unchecked.
There are two kinds of exceptions: checked and unchecked. Unchecked exceptions extend the class RuntimeException
or Error
.
Why have two kinds of exceptions? A checked exception describes a problem that is likely to occur at times, no matter how careful you are. The unchecked exceptions, on the other hand, are your fault. For example, an unexpected end of file can be caused by forces beyond your control, such as a disk error or a broken network connection. But you are to blame for a NullPointerException
, because your code was wrong when it tried to use a null
reference.
Checked exceptions are due to external circumstances that the programmer cannot prevent. The compiler checks that your program handles these exceptions.
The compiler doesn't check whether you handle a NullPointerException
, because you should test your references for null
before using them rather than install a handler for that exception. The compiler does insist that your program be able to handle error conditions that you cannot prevent.
Actually, those categories aren't perfect. For example, the Scanner.nextInt
method throws an unchecked InputMismatchException
if the input does not contain a valid integer. A checked exception would have been more appropriate because the programmer cannot prevent users from entering incorrect input. (The designers of the Scanner
class made this choice to make it easy to use for beginning programmers.)
As you can see from Figure 1, the majority of checked exceptions occur when you deal with input and output. That is a fertile ground for external failures beyond your control—a file might have been corrupted or removed, a network connection might be overloaded, a server might have crashed, and so on. Therefore, you will need to deal with checked exceptions principally when programming with files and streams.
You have seen how to use the Scanner
class to read data from a file, by passing a File
object to the Scanner
constructor:
String filename = ...; File inFile = new File(filename); Scanner in = new Scanner(inFile);
However, the Scanner
constructor can throw a FileNotFoundException
. The FileNotFoundException
is a checked exception, so you need to tell the compiler what you are going to do about it. You have two choices. You can handle the exception, using the techniques that you will see in Section 11.5. Or you can simply tell the compiler that you are aware of this exception and that you want your method to be terminated when it occurs. The method that reads input does not usually know what to do about an unexpected error, so that is usually the better option.
To declare that a method should be terminated when a checked exception occurs within it, tag the method with a throws
clause.
public void read(String filename) throws FileNotFoundException { File inFile = new File(filename); Scanner in = new Scanner(inFile); ... }
The throws
clause in turn signals the caller of your method that it may encounter a FileNotFoundException
. Then the caller needs to make the same decision—handle the exception, or tell its caller that the exception may be thrown.
Add a throws
clause to a method that can throw a checked exception.
If your method can throw exceptions of different types, you separate the exception class names by commas:
public void read(String filename) throws FileNotFoundException, NoSuchElementException
Always keep in mind that exception classes form an inheritance hierarchy. For example, FileNotFoundException
is a subclass of IOException
. Thus, if a method can throw both an IOException
and a FileNotFoundException
, you only tag it as throws IOException
.
It sounds somehow irresponsible not to handle an exception when you know that it happened. Actually, though, it is usually best not to catch an exception if you don't know how to remedy the situation. After all, what can you do in a low-level read
method? Can you tell the user? How? By sending a message to System.out
? You don't know whether this method is called in a graphical program or an embedded system (such as a vending machine), where the user may never see System.out
. And even if your users can see your error message, how do you know that they can understand English? Your class may be used to build an application for users in another country. If you can't tell the user, can you patch up the data and keep going? How? If you set a variable to zero, null
, or an empty string, that may just cause the program to break later, with much greater mystery.
Of course, some methods in the program know how to communicate with the user or take other remedial action. By allowing the exception to reach those methods, you make it possible for the exception to be processed by a competent handler.
9. Why is a NullPointerException
not a checked exception?
Every exception should be handled somewhere in your program. If an exception has no handler, an error message is printed, and your program terminates. That may be fine for a student program. But you would not want a professionally written program to die just because some method detected an unexpected error. Therefore, you should install exception handlers for all exceptions that your program might throw.
In a method that is ready to handle a particular exception type, place the statements that can cause the exception inside a try
block, and the handler inside a catch
clause.
You install an exception handler with the try
/catch
statement. Each try
block contains one or more statements that may cause an exception. Each catch
clause contains the handler for an exception type. Here is an example:
try { String filename = ...; File inFile = new File(filename); Scanner in = new Scanner(inFile); String input = in.next(); int value = Integer.parseInt(input); ...
} catch (IOException exception) { exception.printStackTrace(); } catch (NumberFormatException exception) { System.out.println("Input was not a number"); }
Three exceptions may be thrown in this try
block: The Scanner
constructor can throw a FileNotFoundException, Scanner.next
can throw a NoSuchElementException
, and Integer.parseInt
can throw a NumberFormatException
.
If any of these exceptions is actually thrown, then the rest of the instructions in the try
block are skipped. Here is what happens for the various exception types:
If a FileNotFoundException
is thrown, then the catch
clause for the IOException
is executed. (Recall that FileNotFoundException
is a subclass of IOException
.)
If a NumberFormatException
occurs, then the second catch
clause is executed.
A NoSuchElementException
is not caught by any of the catch
clauses. The exception remains thrown until it is caught by another try
block or the main
method terminates.
When the catch (IOException exception)
block is executed, then some method in the to the exception object that was thrown. The catch
clause can analyze that object to find out more details about the failure. For example, you can get a printout of the chain of method calls that lead to the exception, by calling
exception.printStackTrace()
In these sample catch
clauses, we merely inform the user of the source of the problem. A better way of dealing with the exception would be to give the user another chance to provide a correct input—see Section 11.8 for a solution.
It is important to remember that you should place catch
clauses only in methods in which you can competently handle the particular exception type.
11. Is there a difference between catching checked and unchecked exceptions?
Occasionally, you need to take some action whether or not an exception is thrown. The finally
construct is used to handle this situation. Here is a typical situation.
It is important to close a PrintWriter
to ensure that all output is written to the file. In the following code segment, we open a stream, call one or more methods, and then close the stream:
PrintWriter out = new PrintWriter(filename); writeData(out); out.close(); // May never get here
Now suppose that one of the methods before the last line throws an exception. Then the call to close
is never executed! Solve this problem by placing the call to close
inside a finally
clause:
PrintWriter out = new PrintWriter(filename); try { writeData(out); } finally { out.close(); }
In a normal case, there will be no problem. When the try
block is completed, the finally
clause is executed, and the writer is closed. However, if an exception occurs, the finally
clause is also executed before the exception is passed to its handler.
Use the finally
clause whenever you need to do some clean up, such as closing a file, to ensure that the clean up happens no matter how the method exits.
It is also possible to have a finally
clause following one or more catch
clauses. Then the code in the finally
clause is executed whenever the try
block is exited in any of three ways:
After completing the last statement of the try
block
After completing the last statement of a catch
clause, if this try
block caught an exception
When an exception was thrown in the try
block and not caught
However, we recommend that you don't mix catch
and finally
clauses in the same try
block—see Quality Tip 11.3.
Once a try
block is entered, the statements in a finally
clause are guaranteed to be executed, whether or not an exception is thrown.
13. Suppose the file with the given name does not exist. Trace the flow of execution of the code segment in this section.
Sometimes none of the standard exception types describe your particular error condition well enough. In that case, you can design your own exception class. Consider a bank account. Let's report an InsufficientFundsException
when an attempt is made to withdraw an amount from a bank account that exceeds the current balance.
if (amount > balance) { throw new InsufficientFundsException( "withdrawal of " + amount + " exceeds balance of " + balance); }
Now you need to provide the InsufficientFundsException
class. Should it be a checked or an unchecked exception? Is it the fault of some external event, or is it the fault of the programmer? We take the position that the programmer could have prevented the exceptional condition—after all, it would have been an easy matter to check whether amount <= account.getBalance()
before calling the withdraw
method. Therefore, the exception should be an unchecked exception and extend the Runtime-Exception
class or one of its subclasses.
To describe an error condition, provide a subclass of an existing exception class.
It is a good idea to extend an appropriate class in the exception hierarchy. For example, we can consider an InsufficientFundsException
a special case of an Illegal-ArgumentException
. This enables other programmers to catch the exception as an IllegalArgumentException
if they are not interested in the exact nature of the problem.
It is customary to provide two constructors for an exception class: a constructor with no parameters and a constructor that accepts a message string describing the reason for the exception. Here is the declaration of the exception class.
public class InsufficientFundsException extends IllegalArgumentException { public InsufficientFundsException() {} public InsufficientFundsException(String message) { super(message); } }
When the exception is caught, its message string can be retrieved using the get-Message
method of the Throwable
class.
15. Suppose you read bank account data from a file. Contrary to your expectation, the next input value is not of type double
. You decide to implement a BadData-Exception
. Which exception class should you extend?
This section walks through a complete example of a program with exception handling. The program asks a user for the name of a file. The file is expected to contain data values. The first line of the file contains the total number of values, and the remaining lines contain the data. A typical input file looks like this:
3 1.45 -2.1 0.05
What can go wrong? There are two principal risks.
The file might not exist.
The file might have data in the wrong format.
Who can detect these faults? The Scanner
constructor will throw an exception when the file does not exist. The methods that process the input values need to throw an exception when they find an error in the data format.
What exceptions can be thrown? The Scanner
constructor throws a FileNotFoundException
when the file does not exist, which is appropriate in our situation. Finally, when the file data is in the wrong format, we will throw a BadDataException
, a custom checked exception class. We use a checked exception because corruption of a data file is beyond the control of the programmer.
Who can remedy the faults that the exceptions report? Only the main
method of the DataAnalyzer
program interacts with the user. It catches the exceptions, prints appropriate error messages, and gives the user another chance to enter a correct file.
ch11/data/DataAnalyzer.java
1
import java.io.FileNotFoundException;2
import java.io.IOException;3
import java.util.Scanner;4
5
/**6
This program reads a file containing numbers and analyzes its contents.7
If the file doesn't exist or contains strings that are not numbers, an8
error message is displayed.9
*/10
public class DataAnalyzer11
{12
public static void main(String[] args)13
{14
Scanner in = new Scanner(System.in);15
DataSetReader reader = new DataSetReader();16
17
boolean done = false;18
while (!done)19
{20
try21
{22
System.out.println("Please enter the file name: ");23
String filename = in.next();24
25
double[] data = reader.readFile(filename);26
double sum = 0;27
for (double d : data) sum = sum + d;28
System.out.println("The sum is " + sum);29
done = true;30
}31
catch (FileNotFoundException exception)32
{33
System.out.println("File not found.");34
}35
catch (BadDataException exception)36
{37
System.out.println("Bad data: " + exception.getMessage());38
}39
catch (IOException exception)40
{41
exception.printStackTrace();42
}43
}44
}45
}
The catch
clauses in the main
method give a human-readable error report if the file was not found or bad data was encountered.
The following readFile
method of the DataSetReader
class constructs the Scanner
object and calls the readData
method. It is completely unconcerned with any exceptions. If there is a problem with the input file, it simply passes the exception to its caller.
public double[] readFile(String filename) throws IOException { File inFile = new File(filename); Scanner in = new Scanner(inFile); try { readData(in); return data; } finally { in.close(); } }
The method throws an IOException
, the common superclass of FileNotFoundException
(thrown by the Scanner
constructor) and BadDataException
(thrown by the readData
method).
Next, here is the readData
method of the DataSetReader
class. It reads the number of values, constructs an array, and calls readValue
for each data value.
private void readData(Scanner in) throws BadDataException { if (!in.hasNextInt()) throw new BadDataException("Length expected"); int numberOfValues = in.nextInt(); data = new double[numberOfValues]; for (int i = 0; i < numberOfValues; i++) readValue(in, i); if (in.hasNext()) throw new BadDataException("End of file expected"); }
This method checks for two potential errors. The file might not start with an integer, or it might have additional data after reading all values.
However, this method makes no attempt to catch any exceptions. Plus, if the readValue
method throws an exception—which it will if there aren't enough values in the file—the exception is simply passed on to the caller.
Here is the readValue
method:
private void readValue(Scanner in, int i) throws BadDataException { if (!in.hasNextDouble()) throw new BadDataException("Data value expected"); data[i] = in.nextDouble(); }
To see the exception handling at work, look at a specific error scenario.
DataAnalyzer.main
calls DataSetReader.readFile.
readFile
calls readData
.
readData
calls readValue
.
readValue
doesn't find the expected value and throws a BadDataException
.
readValue
has no handler for the exception and terminates immediately.
readData
has no handler for the exception and terminates immediately.
7. readFile
has no handler for the exception and terminates immediately after executing the finally
clause and closing the Scanner
object.
DataAnalyzer.main
has a handler for a BadDataException
. That handler prints a message to the user. Afterwards, the user is given another chance to enter a file name. Note that the statements computing the sum of the values have been skipped.
This example shows the separation between error detection (in the DataSetReader. readValue
method) and error handling (in the DataAnalyzer.main
method). In between the two are the readData
and readFile
methods, which just pass exceptions along.
ch11/data/DataSetReader.java
1
import java.io.File;2
import java.io.IOException;3
import java.util.Scanner;4
5
/**6
Reads a data set from a file. The file must have the format7
numberOfValues8
value19
value210
...11
*/12
public class DataSetReader13
{14
private double[] data;15
16
/**17
Reads a data set.18
@param filename the name of the file holding the data19
@return the data in the file20
*/21
public double[] readFile(String filename) throws IOException22
{23
File inFile = new File(filename);24
Scanner in = new Scanner(inFile);25
26
try27
{28
readData(in);29
return data;30
}31
finally32
{33
in.close();34
}35
}36
37
/**38
Reads all data.39
@param in the scanner that scans the data40
*/41
private void readData(Scanner in) throws BadDataException42
{43
if (!in.hasNextInt())44
throw new BadDataException("Length expected");45
int numberOfValues = in.nextInt();
46
data = new double[numberOfValues];47
48
for (int i = 0; i < numberOfValues; i++)49
readValue(in, i);50
51
if (in.hasNext())52
throw new BadDataException("End of file expected");53
}54
55
/**56
Reads one data value.57
@param in the scanner that scans the data58
@param i the position of the value to read59
*/60
private void readValue(Scanner in, int i) throws BadDataException61
{62
if (!in.hasNextDouble())63
throw new BadDataException("Data value expected");64
data[i] = in.nextDouble();65
}66
}
ch11/data/BadDataException.java
1
import java.io.IOException;2
3
/**4
This class reports bad input data.5
*/6
public class BadDataException extends IOException7
{8
public BadDataException() {}9
public BadDataException(String message)10
{11
super(message);12
}13
}
17. Suppose the user specifies a file that exists and is empty. Trace the flow of execution.
Read and write text that is stored in files.
When reading text files, use the Scanner
class.
When writing text files, use the PrintWriter
class.
You must close a print stream when you are done writing output.
The JFileChooser
dialog box allows users to select a file by navigating through directories.
A File
object describes a file or directory.
When you launch a program from the command line, you can specify arguments after the program name. The program can access these strings by processing the args
parameter of the main
method.
Choose an appropriate mechanism for processing input.
The next
method reads a word at a time. Call Scanner.useDelimiter
to specify a pattern for word boundaries.
The nextLine
method reads a line of input and consumes the newline character at the end of the line.
The nextInt
and nextDouble
methods consume white space and the next number.
To read one character at a time, set the delimiter pattern to the empty string.
Understand when and how to throw an exception.
To signal an exceptional condition, use the throw
statement to throw an exception object.
When you throw an exception, the current method terminates immediately.
Choose between checked and unchecked exceptions.
There are two kinds of exceptions: checked and unchecked. Unchecked exceptions extend the class RuntimeException
or Error
.
Checked exceptions are due to external circumstances that the programmer cannot prevent. The compiler checks that your program handles these exceptions.
Add a throws
clause to a method that can throw a checked exception.
Use exception handlers to decouple error detection and error reporting.
In a method that is ready to handle a particular exception type, place the statements that can cause the exception inside a try
block, and the handler inside a catch
clause.
Throw an exception as soon as a problem is detected. Catch it only when the problem can be handled.
Use the finally clause to ensure that resources are released when an exception is thrown.
Once a try
block is entered, the statements in a finally
clause are guaranteed to be executed, whether or not an exception is thrown.
Design exception types to describe error conditions.
To describe an error condition, provide a subclass of an existing exception class.
java.io.EOFException java.lang.RuntimeException java.io.File java.lang.Throwable java.io.FileNotFoundException getMessage java.io.IOException printStackTrace java.io.PrintWriter java.util.NoSuchElementException close java.util.Scanner java.lang.Error close java.lang.IllegalArgumentException javax.swing.JFileChooser java.lang.IllegalStateException getSelectedFile java.lang.NullPointerException showOpenDialog java.lang.NumberFormatException showSaveDialog
R11.1 What happens if you try to open a file for reading that doesn't exist? What happens if you try to open a file for writing that doesn't exist?
R11.2 What happens if you try to open a file for writing, but the file or device is write-protected (sometimes called read-only)? Try it out with a short test program.
R11.3 How do you open a file whose name contains a backslash, like c: empoutput.dat
?
R11.4 What is a command line? How can a program read its command line arguments?
R11.5 Give two examples of programs on your computer that read arguments from the command line.
R11.6 If a program Woozle is started with the command
java Woozle -Dname=piglet -Ieeyore -v heff.txt a.txt lump.txt
what are the values of args[0], args[1]
, and so on?
R11.7 What is the difference between throwing an exception and catching an exception?
R11.8 What is a checked exception? What is an unchecked exception? Is a NullPointer-Exception
checked or unchecked? Which exceptions do you need to declare with the throws
reserved word?
R11.9 Why don't you need to declare that your method might throw a NullPointer-Exception
?
R11.10 When your program executes a throw
statement, which statement is executed next?
R11.11 What happens if an exception does not have a matching catch
clause?
R11.12 What can your program do with the exception object that a catch
clause receives?
R11.13 Is the type of the exception object always the same as the type declared in the catch
clause that catches it?
R11.14 What kind of values can you throw? Can you throw a string? An integer?
R11.15 What is the purpose of the finally
clause? Give an example of how it can be used.
R11.16 What happens when an exception is thrown, the code of a finally
clause executes, and that code throws an exception of a different kind than the original one? Which one is caught by a surrounding catch
clause? Write a sample program to try it out.
R11.17 Which exceptions can the next
and nextInt
methods of the Scanner
class throw? Are they checked exceptions or unchecked exceptions?
R11.18 Suppose the code in Quality Tip 11.3 on page 489 had been condensed to a single try/catch/finally
statement:
PrintWriter out = new PrintWriter(filename); try {Write output
} catch (IOException exception) {Handle exception
} finally { out.close(); }
What is the disadvantage of this version? (Hint: What happens when the PrintWriter
constructor throws an exception?) Why can't you solve the problem by moving the declaration of the out variable inside the try
block?
R11.19 Suppose the program in Section 11.8 reads a file containing the following values:
0 1 2 3
What is the outcome? How could the program be improved to give a more accurate error report?
R11.20 Can the readFile
method in Section 11.8 throw a NullPointerException
? If so, how?
P11.1 Write a program that asks a user for a file name and prints the number of characters, words, and lines in that file.
P11.2 Write a program that asks the user for a file name and counts the number of characters, words, and lines in that file. Then the program asks for the name of the next file. When the user enters a file that doesn't exist, the program prints the total counts of characters, words, and lines in all processed files and exits.
P11.3 Write a program CopyFile
that copies one file to another. The file names are specified on the command line. For example,
java CopyFile report.txt report.sav
P11.4 Write a program that concatenates the contents of several files into one file. For example,
java CatFiles chapter1.txt chapter2.txt chapter3.txt book.txt
makes a long file, book.txt
, that contains the contents of the files chapter1.txt, chapter2.txt
, and chapter3.txt
. The output file is always the last file specified on the command line.
P11.5 Write a program Find
that searches all files specified on the command line and prints out all lines containing a reserved word. For example, if you call
java Find ring report.txt address.txt Homework.java
then the program might print
report.txt: has broken up an international ring of DVD bootleggers that address.txt: Kris Kringle, North Pole address.txt: Homer Simpson, Springfield Homework.java: String filename;
The reserved word is always the first command line argument.
P11.6 Write a program that checks the spelling of all words in a file. It should read each word of a file and check whether it is contained in a word list. A word list is available on most UNIX systems in the file /usr/dict/words
. (If you don't have access to a UNIX system, your instructor should be able to get you a copy.) The program should print out all words that it cannot find in the word list.
P11.7 Write a program that replaces each line of a file with its reverse. For example, if you run
java Reverse HelloPrinter.java
then the contents of HelloPrinter.java
are changed to
retnirPolleH ssalc cilbup { )sgra ][gnirtS(niam diov citats cilbup { wodniw elosnoc eht ni gniteerg a yalpsiD // ;)"!dlroW, olleH"(nltnirp.tuo.metsyS } }
Of course, if you run Reverse
twice on the same file, you get back the original file.
P11.8 Get the data for names in prior decades from the Social Security Administration. Paste the table data in files named babynames80s.txt
, etc. Modify the BabyNames.java
program so that it prompts the user for a file name. The numbers in the files have comma separators, so modify the program to handle them. Can you spot a trend in the frequencies?
P11.9 Write a program that reads in babynames.txt
and produces two files boynames.txt
and girlnames.txt
, separating the data for the boys and girls.
P11.10 Write a program that reads a file in the same format as babynames.txt
and prints all names that are both boy and girl names (such as Alexis or Morgan).
P11.11 Write a program that replaces all tab characters ' '
in a file with the appropriate number of spaces. By default, the distance between tab columns should be 3 (the value we use in this book for Java programs) but it can be changed by the user. Expand tabs to the number of spaces necessary to move to the next tab column. That may be less than three spaces. For example, consider the line containing " | || |"
. The first tab is changed to three spaces, the second to two spaces, and the third to one space. Your program should be executed as
java TabExpander filename
or
java TabExpander -ttabwidth
filename
P11.12 Modify the BankAccount
class to throw an IllegalArgumentException
when the account is constructed with a negative balance, when a negative amount is deposited, or when an amount that is not between 0 and the current balance is withdrawn. Write a test program that causes all three exceptions to occur and that catches them all.
P11.13 Repeat Exercise P11.12, but throw exceptions of three exception types that you provide.
P11.14 Write a program that asks the user to input a set of floating-point values. When the user enters a value that is not a number, give the user a second chance to enter the value. After two chances, quit reading input. Add all correctly specified values and print the sum when the user is done entering data. Use exception handling to detect improper inputs.
P11.15 Repeat Exercise P11.14, but give the user as many chances as necessary to enter a correct value. Quit the program only when the user enters a blank input.
P11.16 Modify the DataSetReader
class so that you do not call hasNextInt
or hasNextDouble
. Simply have nextInt
and nextDouble
throw a NoSuchElementException
and catch it in the main
method.
P11.17 Write a program that reads in a set of coin descriptions from a file. The input file has the format
coinName1 coinValue1 coinName2 coinValue2 ...
Add a method
void read(Scanner in) throws FileNotFoundException
to the Coin
class. Throw an exception if the current line is not properly formatted. Then implement a method
static ArrayList<Coin> readFile(String filename) throws FileNotFoundException
In the main
method, call readFile
. If an exception is thrown, give the user a chance to select another file. If you read all coins successfully, print the total value.
P11.18 Design a class Bank
that contains a number of bank accounts. Each account has an account number and a current balance. Add an accountNumber
field to the BankAccount
class. Store the bank accounts in an array list. Write a readFile
method of the Bank
class for reading a file with the format
accountNumber1 balance1 accountNumber2 balance2 ...
Implement read
methods for the Bank
and BankAccount
classes. Write a sample program to read in a file with bank accounts, then print the account with the highest balance. If the file is not properly formatted, give the user a chance to select another file.
Project 11.1 You can read the contents of a web page with this sequence of commands.
String address = "http://java.sun.com/index.html"; URL u = new URL(address); Scanner in = new Scanner(u.openStream()); ...
Some of these methods may throw exceptions—check out the API documentation. Design a class LinkFinder
that finds all hyperlinks of the form
<a href="link
">link text
</a>
Throw an exception if you find a malformed hyperlink. Extra credit if your program can follow the links that it finds and find links in those web pages as well. (This is the method that search engines such as Google use to find web sites.)
When the PrintWriter
object is created, the output file is emptied. Sadly, that is the same file as the input file. The input file is now empty and the while
loop exits immediately.
The Scanner
constructor throws a FileNotFoundException
, and the program terminates.
number
is 6, input
is ",995.0"
price
is set to 6 because the comma is not considered a part of a floating-point number in Java. Then the call to nextInt
causes an exception, and quantity
is not set.
Read them as strings, and convert those strings to numbers that are not equal to N/A
:
String input = in.next(); if (!input.equals("N/A")) { double value = Double.parseDouble(input); Process value }
Throw an exception if the amount being deposited is less than zero.
The balance is still zero because the last statement of the withdraw
method was never executed.
You must include the FileNotFoundException
and you may include the NoSuchElementException
if you consider it important for documentation purposes. InputMismatchException
is a subclass of NoSuchElementException
. It is your choice whether to include it.
Because programmers should simply check for null
pointers instead of trying to handle a NullPointerException
.
The Scanner
constructor succeeds, and in
is constructed. Then the call in.next()
throws a NoSuchElementException
, and the try
block is aborted. None of the catch clauses match, so none are executed. If none of the enclosing method calls catch the exception, the program terminates.
No—you catch both exception types in the same way, as you can see from the code example on page 485. Recall that IOException
is a checked exception and NumberFormatException
is an unchecked exception.
If it had been declared inside the try
block, its scope would only have extended to the end of the try
block, and the finally
clause could not have closed it.
The PrintWriter
constructor throws an exception. The assignment to out
and the try
block are skipped. The finally
clause is not executed. This is the correct behavior because out
has not been initialized.
To pass the exception message string to the RuntimeException
superclass.
Because file corruption is beyond the control of the programmer, this should be a checked exception, so it would be wrong to extend RuntimeException
or Illegal-ArgumentException
. Because the error is related to input, IOException
would be a good choice.
It would not be able to do much with them. The DataSetReader
class is a reusable class that may be used for systems with different languages and different user interfaces. Thus, it cannot engage in a dialog with the program user.
DataAnalyzer.main
calls DataSetReader.readFile
, which calls readData
. The call in.hasNextInt()
returns false
, and readData
throws a BadDataException
. The read-File
method doesn't catch it, so it propagates back to main
, where it is caught.
3.137.163.197