[JAVA-2j] Building a Simple Calculator: Using Try-Catch to Gracefully Handle User Mistakes

Jack Boyuan Xu
7 min readJan 29, 2020

--

Last time we talked about prompting the user again if the user has entered a numerical choice that is out of range. However, as we had shown, our simple calculator still hard crashes if the user inputs anything other than a number. Let’s go about fixing that!

Amazon Prime Video and Chill :)

Bonjour madame, I know we’ve just met but let’s Amazon Prime Video and chill at my place tonight :). We can watch three middle-aged men having some chance of falling over and catching fire. You take a shot for every time they fall over and I take a shot for every time they catch fire. Qu’est-ce que tu penses? Oui? Super! Let’s translate all that into pseudocode then!

Amazon Prime Video and chill () {
let us {
Watch 3 men mess about
} and in case they fall over {
You take a shot
} and in case they catch fire {
I take a shot
}
}

I imagine this is pretty straightforward. What a romantic night! I’d like to spend more nights with you doing this, so I have decided to generalize our Amazon Prime Video and chill nights into the following:

Amazon Prime Video and chill () {
let us {
Watch people do daft or dangerous things
} and in case something goes wrong {
We do something
} and in case another thing goes wrong {
We do something
}
}

This is basically how try-catch blocks work in principle. We do something that is likely to throw an error, and we deal with each one of those thrown errors accordingly. Let’s generalize more and incorporate try-catch into our pseudocode above:

Generalized edgy try-catch () {
try {
Do something that's likely to go wrong
} catch (something goes wrong) {
We do something
} catch (some other thing goes wrong) {
We do something
}
}

Now we are ready to bring this into our simple calculator!

Back to Reality, aka Simple Calculator

Did you really think I can just hit some girl up and invite her to spend an evening with me with my pseudo-French? Of course it won’t happen because she probably doesn’t understand French and more importantly, I don’t drink alcohol. Anyway, recall this crash from last time:

The red error log is called a stack trace. Let’s dissect this stack trace. Pay attention to the bolded line:

Exception in thread "main" java.lang.NumberFormatException: For input string: "a"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at com.boyuanxu.SimpleCalculator.askUserForArithmeticChoice(SimpleCalculator.java:38)
at com.boyuanxu.SimpleCalculator.runCalculator(SimpleCalculator.java:53)
at com.boyuanxu.Main.main(Main.java:7)

Observe how the stack trace goes up a level each time in terms of scope (from a specific method in our program to its enclosing method, and eventually to the main method which encloses the enclosing method of our specific method). We can catch the exception anywhere along the way but if we don’t deal with it at all, the program will crash.

The stack trace tells us exactly how and where the crash happened. It is called a trace because we can see how the crashed happened from start to finish. Here, the trace shows the type of our crash is java.lang.NumberFormatException and it originated from line 38 in our SimpleCalculator.java, which is int arithmeticChoice = Integer.parseInt(userInput);. This is confirmed by the trace above it, showing the crash having something to do with Integer.parseInt.

The method Integer.parseInt(String s) takes in a String and attempts to convert it into an int. It works well for numbers such as 7 or 23, but not so much for letters like V. Therefore, it crashed when we told it to convert a into a number, which obviously would never work.

Note: Some languages such as C has the ability to convert characters (NOT strings!) into numbers. We won’t talk about it here.

Enter Try-Catch, in Java.

Let’s try to deal with the user inputting a non-number and crashing the program using a try-catch block:

...
try {
int arithmeticChoice = Integer.parseInt(userInput);
} catch (NumberFormatException e) {
// Deal with it
}
...

You may be asking, what is this e in the catch block? If you squint a little, the catch block looks suspiciously like a method, and you'd be right. You can think of it as a method and e is simply the parameter. Just like when we declare methods, we can name it whatever we want, such as NumberFormatException Jeff (Yes, it is actually valid.)

We are given e as a means of accessing more details regarding the thrown exception. In IntelliJ, if we type e., we will see quite a lot of methods pop up. Most notably, e.printStackTrace() because it is exactly what happens if an exception doesn't get handled: it prints the stack trace and crashes the program. In general, while e.printStackTrace() gives a lot of useful information for developers, it is not very user-friendly, so we will replace it with something the average user can understand better, such as printing out "Please input a number!".

However, the code snippet above will immediately lead to problems with scope because we need to use this variable later in the method (try making the change in IntelliJ yourself!). We also want to prompt the user again and again until they give a valid input. Basically, just like recursion, we want to repeat the entire process until the user gets it right. Therefore, it is in our best interest to put the entire method inside of the try block and in catch, just call the method itself again. This way if there is no error, then the method returns the user’s choice. If the user didn’t give a valid input, the method itself is called again and the user is prompted again. Here is the modified method:

public int askUserForArithmeticChoice() {
try {
System.out.println("What arithmetic operation would you like to perform?");
System.out.println("1. Addition");
System.out.println("2. Subtraction");
System.out.println("3. Multiplication");
System.out.println("4. Division");
String userInput = s.nextLine();
int arithmeticChoice = Integer.parseInt(userInput);
if (arithmeticChoice >= 1 && arithmeticChoice <= 4) {
return arithmeticChoice;
} else {
System.out.println("Invalid choice!");
return askUserForArithmeticChoice();
}
} catch (NumberFormatException e) {
System.out.println("Please input a number!");
return askUserForArithmeticChoice();
}
}

Run our calculator again and we can see that it won’t crash anymore:

Note: signal 2: SIGINT means I manually terminated the program. It is not a crash.

However, our program still crashes in other places when the user is prompted for inputs. Let’s fix them as well!

The Try-Catch Treatment

Here is another crash we need to deal with, caused by the line double number1 = Double.parseDouble(userInput);:

Let’s go ahead and wrap it in a try-catch block:

However, there are two problems:

  1. The variable number1 is out of scope for later use.
  2. 2. We are not able to prompt the user again.

We can solve these issues by encapsulating the relevant code into a new method:

public double askForFirstNumber() {
try {
System.out.print("Please enter the first number: ");
String userInput = s.nextLine();
return Double.parseDouble(userInput);
} catch (NumberFormatException e) {
System.out.println("Please input a number!");
return askForFirstNumber();
}
}

And in the main method, replace everything we removed with this one line:

double number1 = askForFirstNumber();

We can do the same for when we ask for the second number:

Note: Yes, there is a lot of code repetition. Can we do better and condense the two methods into one? I’ll leave that one to you!

Now our code is MUCH cleaner! Let’s go ahead and run it:

Voila! Our simple calculator is now bulletproof.

This marks the completion of our first Java program! It is a proper simple calculator with sufficient levels of error checking. While it may not be production quality, it is definitely something to be proud of. In the next article, we will wrap up everything and take a look back.

--

--

Jack Boyuan Xu

Co-founder & Tech Lead @ EthSign. Blockchain Lecturer @ USC.