Exceptions and try..catch in Java
A try..catch block lets a program continue after a runtime error. When an exception occurs, the rest of the try block is skipped and the catch clause runs.
A Program That Must Survive a Bad File
Imagine a small program that reads numbers from a file whose name the user types in. It then reports how many numbers it read, their sum, and their average. This is the program used in this exercise, named AverageNumbersFromFile. It sounds simple, but the file can be bad in two different ways. The file name may not point to a readable file, and the file may contain something other than numbers of type double. A program that does not plan for either situation would stop at the first problem. The program is designed to survive both, and the tool that makes this possible is the try..catch block.
What do you think happens?
If the file holds the values 3.5, 4.0, a token that is not a number, and then 10.0, how many numbers does the program report as read?
Reveal answer
Two, because the loop stops at the first value that cannot be read as a double
The source states that the output is the sum and average of the values read successfully before the first problem. The 10.0 after the bad token is never reached, so it is not included.
What a Runtime Error Is
A runtime error is a problem that appears while the program is running, not a mistake that prevents the program from compiling. Reading a file is a common source of runtime errors. The file may not exist, may not be readable, or may contain data in an unexpected form.
Without any handling, one of these problems would stop the program. Exception handling gives the program a way to respond to such a problem and then keep going. The phrase exception handling describes the whole approach, and try..catch is the Java syntax that carries it out.
A try..catch block lets a program continue after a runtime error. When an exception occurs, the rest of the try block is skipped and the catch clause runs.
The Shape of a try..catch Handler
A try..catch handler has two parts. The try part holds the statements that might fail. The catch part names the kind of exception it handles and holds the code that runs when that exception occurs. The schematic below uses the general form only. The exercise's own calls are described in the sections that follow.
- The try part is the code the program attempts.
- The catch part names the exception type it handles.
- The catch body runs only when that exception is thrown.
The exercise uses this same pattern twice. The call to TextIO.readFile is in the try part of the first handler, and IllegalArgumentException is caught in its catch part. The second handler wraps the loop that calls TextIO.getDouble() repeatedly. Both handlers catch IllegalArgumentException, but the code in each catch clause is different because the two blocks serve different purposes.
Tracing the Reading Loop
The second try..catch wraps the reading loop. On each pass, the loop calls TextIO.getDouble(). Each successful call is followed by a count increment and an addition to the sum. The clearest way to understand the loop is to watch count and sum change from pass to pass. The example below uses a generated file that holds 3.5, 4.0, a token that cannot be read as a double, and then 10.0.
Reading 3.5, 4.0, a bad token, then 10.0
Starting with count 0 and sum 0.0, trace how the loop updates count and sum, and state the final output.
- 1Pass 1
The value 3.5 is read successfully. The value is stored, count becomes 1, and sum becomes 3.5.
- 2Pass 2
The value 4.0 is read successfully. Count becomes 2, and sum becomes 7.5.
- 3Pass 3
The token cannot be read as a double, so an exception is thrown. The rest of the try block is skipped, so count and sum keep the values 2 and 7.5. Control moves to the catch clause.
- 4After the catch
The loop does not run again. The program moves on to the results, which use count 2 and sum 7.5.
The program reports 2 numbers read, a sum of 7.5, and an average of 3.75. The 10.0 at the end of the file is not included, because the first exception ended the loop.
Where Execution Goes After an Exception
The flowchart below shows the path that execution takes on each pass through the try block. At the decision point, the program asks whether the value was read successfully. If the answer is no, the value is stored, the count goes up, and the value is added to the sum, and then the loop repeats. If the answer is yes, the rest of the try block is skipped. The count and sum keep the values they had at that moment, and control moves directly to the catch clause.
Why the End of File Is Not an Error
The loop reads until the data runs out, and running out of data causes an exception too. The source explains that this end-of-file IllegalArgumentException is expected and is not treated as an error, so its catch clause is intentionally empty. The loop stops, and the program reports its results normally. A genuine runtime error, such as an unreadable file name, is handled differently because the catch clause has real work to do.
| Situation | What the program expects | What the catch clause does | What the user sees |
|---|---|---|---|
| End of file reached in the reading loop | The data simply runs out | Nothing; the catch body is intentionally empty | The results for the numbers read so far |
| File name does not point to a readable file | A real problem that must be addressed | Responds to the problem and lets the program continue | A handled response, not a crash |
| Value in the file is not a double | The data is not in the expected form | Ends the reading loop; the output covers values read before the problem | Sum and average of the values read successfully |
Expected events and genuine errors handled by try..catch in this exercise
When you write your own handler, decide whether the exception represents a real error or an expected event. If the situation is an error, the catch clause should report it or recover from it. If the situation is an expected event, the catch clause may be short. This program uses both kinds of handling, which makes it a useful contrast.
Mistakes Readers Make With Handlers
Common mistake Assuming the failing statement is retried
What it looks likeA reader expects the bad token to be read again on the next pass.
Why it failsWhen an exception occurs, the rest of the try block is skipped and the catch clause runs. Control does not return to the statement that failed.
Do this insteadTrace the program by following the flowchart: after the catch clause, execution moves on to the code after the try..catch.
Common mistake Expecting count and sum to include the value that caused the exception
What it looks likeA reader adds the bad value or the 10.0 after it to the total.
Why it failsThe count and sum keep the values they had at the moment the exception occurred. The values after the problem are never read.
Do this insteadUse the values from before the exception. In the generated example, the result is 2 values and a sum of 7.5.
Common mistake Treating every caught exception as a failure to report
What it looks likeA reader adds an error message to the end-of-file catch clause.
Why it failsThe source states that the end-of-file exception is expected and is not treated as an error, so its catch clause is intentionally empty.
Do this insteadDecide whether the exception is a real error or an expected event before writing the catch body.
Common mistake Forgetting that a runtime error is not a compile-time error
Trace a Different File
A generated file contains the values 2.0, 6.0, 1.0, a token that is not a double, and then 8.0. Trace the reading loop. What count and sum does the program report, and which value is not included?
Hints
- Track count and sum after each successful read, starting from zero.
- The rest of the try block is skipped at the exception, so the values after the bad token are never read.
- The 1.0 is read before the bad token, so it is included in the results.
Explain in one or two sentences why the end-of-file exception and the bad-token exception in the reading loop are handled differently, using the words expected and error.
Hints
- Look at what the source says about the end-of-file catch clause.
- Consider whether the bad token is a real problem in the data.
Key Takeaways
- A try..catch block lets a program continue after a runtime error. When an exception occurs, the rest of the try block is skipped and the catch clause runs.
- After the catch clause finishes, execution does not return to the failing statement. It moves on to the code after the try..catch.
- In the reading loop, count and sum keep the values they had when the exception occurred, so the results cover only the values read successfully before the problem.
- The end-of-file
IllegalArgumentExceptionis expected in this exercise, so its catch clause is intentionally empty, while a genuine error needs a catch clause that responds to it.