Exception Handling with try-catch
The actionPerformed method is the entry point for handling a button press, and it receives an ActionEvent describing that press.
Testing a Common Belief About Handlers
A button handler in a graphical program is easy to picture as a list of steps that always runs from top to bottom. Many beginners assume that if the user types a bad value into one text box, the handler shows an error message and then carries on to compute something anyway. A second version of the same belief says that every kind of mistake inside the handler is caught by the same kind of try-catch block. This article tests both beliefs against a real event handler written for a Swing calculator, which runs when the user presses one of the operator buttons.
What do you think happens?
The user types 8 into the x box, types 0 into the y box, and presses the divide button. Does the handler perform the division?
Reveal answer
No, the division is skipped and an error message is shown
The handler tests the value of y with an if statement before it divides. If y equals 0, it sets the answer label to 'Can't divide by zero!' and the division itself is not performed. Division by zero is therefore not handled with a try-catch block in this design.
How a Button Press Reaches the Handler
The actionPerformed method is the entry point for every button press in this design. The application does not call it directly. Swing calls actionPerformed with an ActionEvent object that describes the interaction, and the method is the place where the application decides what that interaction means. The order of events runs from the user's click, to the event system, to the handler's code, as the diagram below shows.
The handler's code runs only after the event system has delivered the press. Everything the handler does for that press happens inside that one call. When the method returns, that button press is finished, and the next press starts a new call.
Tracing the Handler's Decisions
The handler has two jobs that happen in sequence. The first job turns the two text boxes into numbers. The second job picks one arithmetic operation based on the button that was pressed and writes its result. Keeping these jobs separate makes it easier to see where a failure can occur and what the user should see afterward.
Read the diagram as a set of exits. The x test and the y test lead to error terminals that stop the handler before any arithmetic happens. The operator test has one branch that is not a simple match: divide passes through an extra check on y before any division is performed. Only the paths that reach the computed result terminal are successful button presses. Follow the no arrows from the two parsing tests first, because those are the paths where the method ends early.
The first belief from the introduction is therefore false for this handler. The source's design does not continue after reporting a bad input, because the return ends the method first.
A try Block for Bad Input
The following sketch illustrates the input-reading pattern described in the source. The field names xField, yField, and answerLabel are placeholders chosen for this example, not names taken from the source, and the rest of the class is omitted.
public void actionPerformed(ActionEvent e) {
double x;
try {
x = Double.parseDouble(xField.getText());
} catch (NumberFormatException ex) {
answerLabel.setText('Please enter a number for x');
xField.requestFocusInWindow();
return;
}
// ... y is read the same way, then the operator is chosen
}- 1The method signature is the entry point Swing calls for a button press.
- 2The try block contains the one statement that may fail on bad input.
- 3If parsing fails, the catch clause sets the label, moves focus to the x box, and returns.
- 4If parsing succeeds, control skips the catch clause and continues with the rest of the method.
- The method receives an
ActionEventfrom Swing for each button press Double.parseDoubleis the statement that can fail on illegal text- The catch clause runs only for
NumberFormatException - return ends the method, so the code after the catch never runs for this press
Division by Zero Uses an if Test
The second belief from the introduction says that division by zero is handled like bad input, with a try-catch block around the division. The source does not do this. The comment in the handler says that division by zero produces an error message, and the division branch tests the value of y with an if statement before it divides. The check happens in advance, so the division is never attempted when y is 0.
The two mechanisms look similar from the user's side, since both show a message in the answer label. Inside the handler they differ. A try-catch block reacts after an operation has failed, by jumping to the catch clause. An explicit if test is checked before the risky operation runs, so the operation is simply not attempted. Both can be used to keep a bad computation from running, but only the if test prevents the division from being performed at all.
result = x / y;Division runs even when y is 0
if (y == 0) {
answerLabel.setText('Can't divide by zero!');
} else {
answerLabel.setText(String.valueOf(x / y));
}The y == 0 test runs first, and the division is skipped when it is true
Mistakes Beginners Make
Common mistake Assuming the handler keeps running after a bad input is reported
What it looks likeThe user types abc into the x box, and the programmer expects the handler to show a message and then still compute the answer.
Why it failsEach catch clause ends with return, so the method ends for that button press before any arithmetic happens.
Do this insteadTrace each return statement in order and note that it ends the method for that press.
Common mistake Wrapping every risky operation in try-catch, including division by zero
What it looks likeA try block is placed around x / y so that a zero y is caught as an error.
Why it failsThe source handles division by zero with an explicit y == 0 test before the division, not with a try-catch block.
Do this insteadUse an if test to check the value of y before dividing, and keep try-catch for the input parsing.
Common mistake Forgetting which path reaches the result
What it looks likeA learner sees the answer label updated after a bad input and concludes the error path did not stop the handler.
Why it failsThe label may have been set by an earlier error message, and the successful result is only written on the computed result path.
Do this insteadFind the terminal where the result is written and check which decisions must be true to reach it.
Common mistake Believing that a single try block around the whole handler is required for correct behavior
Practice Tracing a Press
Using the flowchart from this article, trace a press of the divide button in which the user types 12 into the x box and 4 into the y box. List, in order, each decision the handler makes and state which statement writes the final answer.
Hints
- Start at the Button pressed terminal and follow the yes arrow from each parsing test, since both values are legal numbers.
- At the operator test, follow the divide branch, then check whether y equals 0.
- The result is written on the computed result path after the division is performed.
Trace the same handler for a press of the divide button in which the user types abc into the x box and 0 into the y box. Name the one statement that ends the method, and state whether the y == 0 test is ever reached.
Hints
- The x parsing test fails first, which determines where the handler stops.
- Look at which catch clause runs and what it does last.
Key Takeaways
- The
actionPerformedmethod is the entry point for a button press, and Swing calls it with anActionEventdescribing that press. - Input errors from
Double.parseDoubleare handled with try-catch, and each catch clause ends with return, so the handler stops early for that press. - Division by zero is checked with an explicit y == 0 test before the division, not with a try-catch block.
- To find where a handler diverged from what you expected, trace each decision and return statement in order and note which branch ran.