Chapter 11. Input/Output and Exception Handling

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.

Reading and Writing Text Files

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");

Note

When reading text files, use the Scanner class.

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).

Note

When writing text files, use the PrintWriter class.

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();

Note

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 LineNumberer
10 {
11   public static void main(String[] args) throws FileNotFoundException
12   {
13      // Prompt for the input and output file names
14
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 writing
22
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 output
29
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   }
Reading and Writing Text Files

2. What happens when you supply the name of a nonexistent input file to the Line-Numberer program?

Reading Text Input

In the following sections, you will learn how to process complex text input that you often encounter in real life situations.

Reading Words

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);
}

Note

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.

Processing Lines

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.

Note

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.

Processing Lines

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();

Reading Numbers

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();

Note

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

Reading Numbers

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

Reading Numbers

After the first call to nextInt, the input contains

Reading Numbers

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();

Reading Characters

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("");

Note

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
}
Reading Characters
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?

Throwing Exceptions

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?
      ...
   }
}

Note

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.

Note

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.

The Hierarchy of Exception Classes

Figure 11.1. The Hierarchy of Exception Classes

Throwing an Exception

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?

Checked and Unchecked Exceptions

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.

Note

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.

Note

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.

Note

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.

The throws Clause

9. Why is a NullPointerException not a checked exception?

Catching Exceptions

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.

Note

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.

Catching Exceptions

11. Is there a difference between catching checked and unchecked exceptions?

The finally Clause

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:

  1. After completing the last statement of the try block

  2. After completing the last statement of a catch clause, if this try block caught an exception

  3. 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.

Note

Once a try block is entered, the statements in a finally clause are guaranteed to be executed, whether or not an exception is thrown.

The finally Clause

13. Suppose the file with the given name does not exist. Trace the flow of execution of the code segment in this section.

Designing Your Own Exception Types

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.

Note

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.

Designing Your Own Exception Types

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?

Case Study: A Complete Example

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, an
8    error message is displayed.
9 */
10 public class DataAnalyzer
11 {
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          try
21          {
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.

  1. DataAnalyzer.main calls DataSetReader.readFile.

  2. readFile calls readData.

  3. readData calls readValue.

  4. readValue doesn't find the expected value and throws a BadDataException.

  5. readValue has no handler for the exception and terminates immediately.

  6. readData has no handler for the exception and terminates immediately.

  7. 7. readFile has no handler for the exception and terminates immediately after executing the finally clause and closing the Scanner object.

  8. 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 format
7    numberOfValues
8    value1
9    value2
10    ...
11 */
12 public class DataSetReader
13 {
14    private double[] data;
15
16    /**
17       Reads a data set.
18       @param filename  the name of the file holding the data
19       @return the data in the file
20    */
21    public double[] readFile(String filename) throws IOException
22    {
23       File inFile = new File(filename);
24       Scanner in = new Scanner(inFile);
25
26       try
27       {
28          readData(in);
29          return data;
30       }
31       finally
32       {
33          in.close();
34       }
35    }
36
37    /**
38       Reads all data.
39       @param in the scanner that scans the data
40    */
41    private void readData(Scanner in) throws BadDataException
42    {
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 data
58         @param i  the position of the value to read
59      */
60      private void readValue(Scanner in, int i) throws BadDataException
61      {
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 IOException
7 {
8    public BadDataException() {}
9    public BadDataException(String message)
10    {
11       super(message);
12    }
13 }
Case Study: A Complete Example

17. Suppose the user specifies a file that exists and is empty. Trace the flow of execution.

Summary of Learning Objectives

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.

Classes, Objects, and Methods Introduced in this Chapter

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

Media Resources

  • Worked Example Analyzing Baby Names

  • • Lab Exercises

  • Media Resources
  • Media Resources
  • Media Resources

Review Exercises

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?

Programming Exercises

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 -t tabwidth 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.

Programming Projects

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.)

Answers to Self-Check Questions

  1. 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.

  2. The Scanner constructor throws a FileNotFoundException, and the program terminates.

  3. number is 6, input is ",995.0"

  4. 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.

  5. 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
    }
  6. Throw an exception if the amount being deposited is less than zero.

  7. The balance is still zero because the last statement of the withdraw method was never executed.

  8. 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.

  9. Because programmers should simply check for null pointers instead of trying to handle a NullPointerException.

  10. 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.

  11. 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.

  12. 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.

  13. 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.

  14. To pass the exception message string to the RuntimeException superclass.

  15. 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.

  16. 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.

  17. 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.237.79