Using try..catch Statements
A try..catch block separates risky statements from the response to a specific exception type.
When Text Is Not a Number
Many programs receive text from a user, a file, or a web form, and then need to treat part of that text as a number. The text might be a legal number such as 3.5, or it might be something like hello or 4.5.1 that cannot be read as a number at all. A program that simply assumes the text is valid will stop abruptly when it is not. A try..catch statement gives the program a way to attempt the risky conversion and respond to the failure in a controlled manner.
What do you think happens?
Suppose str holds the text abc. When Double.parseDouble is called on that text, which statements inside the try region run, and which run afterward?
Reveal answer
The parse fails at the call, the rest of the try region is skipped, and the catch region runs
Double.parseDouble cannot read abc as a real number, so it throws NumberFormatException at the call itself. Control leaves the try region at that point, so any statement after the failing call inside the try region never runs. The catch region then runs in its place.
Splitting Code into Try and Catch Regions
A try..catch statement divides code into two regions. The try region holds the statements that might fail. In the program used in this article, those are the parsing call and the output that depends on it. The catch region names the kind of failure it is willing to handle, here NumberFormatException, and holds the response to that failure. The parentheses after catch declare a variable e that refers to the exception object. This example does not use e, but the declaration is required by the catch clause.
Notice where the variable x is declared. The declaration double x; appears before the try region, not inside it. That placement allows both regions to assign to the same variable, so the value chosen in either path is still visible after the block finishes.
A Complete Guarded Conversion
The following program is an original illustration of the pattern described in this article. It declares str, declares x before the try region, attempts the parse inside the try region, and assigns Double.NaN in the catch region. Read it once from top to bottom, and ask for each value of str which of the two output statements inside the blocks will run.
public class ParseDemo {
public static void main(String[] args) {
String str = "3.5";
double x;
try {
x = Double.parseDouble(str);
System.out.println("Parsed value: " + x);
} catch (NumberFormatException e) {
x = Double.NaN;
System.out.println("Input was not a number");
}
System.out.println("x is " + x);
}
}- 1String str holds the text to be converted.
- 2double x declares the numeric variable before any risky code runs.
- 3Inside the try region,
Double.parseDoubleattempts to read str as a double and assigns the result to x. - 4When the parse succeeds, the next line prints the parsed value, and the catch region is skipped.
- 5After the block, the final line prints x, which holds 3.5 on this path.
- double x; is declared before the try region so both paths can assign it
- The parse call is the first statement in the try region and the point where failure can occur
- The catch clause names
NumberFormatException, the specific failure it handles - The output shown is for str holding the text 3.5, a legal real number
Parsed value: 3.5
x is 3.5Tracing Both Paths
Normal flow and failure flow are different paths through the same statements. Tracing the program means following one value of str through the code and noting each statement that actually runs. The two subsections below trace the two possible outcomes of the parse.
Why the Catch Block Assigns Double.NaN
After a failed parse there is no legitimate number to store in x. The program still needs x to hold something that later code can read. In Java, Double.NaN is a constant of type double that stands for not-a-number. It is a value a program can carry in a double variable to signal that no usable number was produced. The catch region therefore assigns Double.NaN so that the variable reflects the failure instead of holding a stale or misleading value.
Mistakes Beginners Make with Try and Catch
Common mistake Declaring the variable inside the try region
What it looks liketry { double x = Double.parseDouble(str); } catch (NumberFormatException e) { x = Double.NaN; }Why it failsx declared inside the try region is not visible in the catch region or after the block, so the catch assignment cannot refer to it.
Do this insteadDeclare double x; before the try region so that both paths assign the same variable.
Common mistake Looking for the failure in the output statement
Why it failsThe output statement inside the try region only runs after the parse succeeds, so it cannot be the first point where the path diverged.
Do this insteadTrace back to the statement that can throw, here the parseDouble call, and ask whether it succeeds for the given input.
Common mistake Assuming statements after the failing call still run
Why it failsControl leaves the try region at the point of failure, so the rest of the try region is skipped.
Do this insteadMark the throwing statement as the exit point and treat everything after it inside the try region as skipped on the failure path.
Common mistake Catching a different exception type than the one the call throws
Practice Tracing a Guarded Conversion
Suppose the program from this article is run with str holding the text 4.5.1. Before running it, state which print statements will execute, which statement is the exact point of divergence, and what value x holds at the end. Then explain in one sentence why Double.NaN is assigned.
Hints
- Ask whether 4.5.1 can be read as a single real number by
Double.parseDouble. - Find the first statement in the try region that can throw, and stop tracing the try region there on the failure path.
- Recall that the catch region assigns
Double.NaNwhen no usable number was produced.
Key Takeaways
- A try..catch block separates the risky statements in the try region from the response to a specific exception type in the catch region.
- When
Double.parseDoublethrowsNumberFormatException, control leaves the try region at the failing call, skips the rest of that region, and runs the catch region. - The point of divergence is always the statement that can throw, so tracing starts by asking whether that call succeeds for the given input.
- Declaring the variable before the try region lets both paths assign it, and
Double.NaNmarks that no usable number was produced.