Reading Numbers From the User With TextIO
TextIO reads from a file have no sensible recovery path, so when a read fails it throws an exception rather than fixing the input itself.
Why Keyboard Input and File Input Differ
When a program asks a user for a number, TextIO can keep the user in the loop. If the answer is not a legal number, the program can ask again, and the user simply types a new answer. A file is different. Nobody is sitting at the keyboard to retype a bad line, so the program has to decide what to do on its own. This article follows what happens inside TextIO when reading from a file goes wrong, and how a try..catch lets your program respond instead of stopping.
What do you think happens?
A program reads values from a file, hits a bad value, and no try..catch surrounds the read. What do you think happens?
Reveal answer
An exception is thrown and, with nothing to catch it, the program stops with an error.
TextIO has no sensible recovery path for a file, so it reports the problem by throwing an exception. Only code written to catch that exception can respond to it differently.
What TextIO Does When a File Read Fails
TextIO can read several kinds of input, and a file is one of them. When the source is the user, TextIO checks that a numeric answer is legal and uses a loop-and-try..catch style of checking to get a valid value. When the source is a file, that recovery option is gone. There is no sensible way for TextIO to fix an illegal value sitting in the file, so it reports the problem by throwing an exception instead of correcting the input itself.
The sequence below shows the three parties involved. Your program asks for a value, TextIO reads from the file, and the file hands back something unusable. TextIO does not repair it. It throws the exception, and the exception travels back to your program, which decides how to respond.
The Single Exception Type: IllegalArgumentException
TextIO throws only IllegalArgumentException for file errors, whatever the underlying problem is. Its design is deliberately simple: the error category does not change the exception type. An illegal value in the file and an attempt to read past the end of the data both arrive as the same kind of exception.
Catching a Failed Read With try..catch
A file error does not have to crash the program. If you have a better response than letting it crash, you wrap the reading code in a try block and attach a catch clause for IllegalArgumentException. The catch clause is where your alternative response goes. It might print a message, store a default value, or stop reading gracefully. Because TextIO uses only this one exception type, the catch clause you write is the one that receives every file-reading failure from TextIO.
The flowchart below traces control through a try..catch around a single read. Notice that the rest of the try block is skipped the moment the read fails, while the code after the whole try..catch still runs in both cases.
Catching a bad value instead of crashing
A program reads a value from a file inside a try block. The value is illegal, so TextIO throws IllegalArgumentException. The program should store a default value of 0 and continue rather than stop.
- 1Wrap the read in a try block
The reading code goes inside the try part, so any file-reading failure from TextIO is raised while control is inside this block.
- 2Attach a catch clause for IllegalArgumentException
This is the only exception type TextIO throws for file errors, so this clause receives every file-reading failure from TextIO.
- 3Place the alternative response in the catch clause
Inside the catch clause, the program stores the default value 0 in place of the value it could not read.
- 4Continue after the try..catch
Once the catch clause finishes, control moves to the code after the whole structure, so the program keeps running.
The program does not crash. The unreadable value is replaced by 0, and the remaining code runs normally.
- The try block holds the read; if the read fails, the statements after it inside the block are skipped.
- The catch clause names
IllegalArgumentException, the one type TextIO uses for file errors. - The statement after the closing brace always runs, whether the read succeeded or was caught.
Using the Exception as a Loop Stop Signal
A common file task is averaging numbers when you do not know how many numbers the file holds. The file contains only real numbers, and you want their sum and their average. Since the count is unknown, the program needs a way to decide when to stop. One approach is to keep reading without any planned stopping point and let the read eventually fail at the end of the file.
That failure is not really an error in this situation. Running past the last value is simply how the program learns that the data has ended. So the exception becomes a signal: you catch it, leave the reading loop, and finish the calculation. Putting the read inside a while (true) loop and breaking out when the exception arrives is an example of using an exception as part of the expected flow of control in a program, rather than as a sign that something went wrong.
Tracing a sum and average over a short file
The file contains the real numbers 3, 7, and 5, one per line. The program reads until the data runs out, then reports the sum and average.
- 1Pass 1: read 3
The read succeeds, so 3 is added. Sum is 3, count is 1.
- 2Pass 2: read 7
The read succeeds, so 7 is added. Sum is 10, count is 2.
- 3Pass 3: read 5
The read succeeds, so 5 is added. Sum is 15, count is 3.
- 4Pass 4: read past the end
No value remains, so TextIO throws
IllegalArgumentException. The catch clause leaves the loop instead of adding anything. - 5Compute the average
The average is the sum divided by the count, which is 15 divided by 3.
Sum is 15, average is 5.
Mistakes With Exception-Driven Reading
Common mistake Assuming the exception only means a bad value
Why it failsTextIO throws the same
IllegalArgumentExceptionfor a bad value and for reading past the end of the data, so the exception type alone does not say which happened.Do this insteadStructure the handler so that it works correctly whichever situation caused the exception, or check the data state another way.
Common mistake Expecting TextIO to fix a bad value in the file
Why it failsA file has no user at the keyboard to retype a line, and TextIO has no sensible recovery path, so it reports the problem instead of correcting it.
Do this insteadWrite a catch clause that chooses the response, such as a default value, a message, or a graceful stop.
Common mistake Leaving the read unprotected in a loop with no catch
Why it failsThe end-of-data exception is then never intercepted, so the program stops with an error instead of finishing its calculation.
Do this insteadWrap the read in a try block with a catch clause for IllegalArgumentException, and break out of the loop when the catch runs.
Common mistake Putting code that should run only after a successful read inside the try block below the read, where it will be skipped on failure
Practice Tracing a Read Loop
A file contains the real numbers 10, 4, and 6, one per line. A program reads with a while (true) loop, adds each value to a running sum and count, and breaks out of the loop when IllegalArgumentException is caught. Trace the passes: what is the sum, what is the count, and on which pass does the exception occur?
Hints
- Count how many successful reads happen before the file runs out.
- The exception occurs on the pass after the last value has been read.
- The average is the sum divided by the count.
Key Takeaways
- TextIO has no sensible recovery path for a bad value in a file, so it reports the problem by throwing an exception rather than fixing the input.
- TextIO throws only
IllegalArgumentExceptionfor file errors, and both a bad value and the end of the data arrive as that same exception type. - A try..catch that catches
IllegalArgumentExceptionlets your program choose its own response, such as printing a message, storing a default, or stopping cleanly. - A loop that reads until the data runs out can use the end-of-data exception as a deliberate stop signal, catching it and leaving the loop before finishing the calculation.