Learn Programming in Java

<<Previous | ToC | Next >>

Unsafe (Native Java) Input

In the previous page we experimented with exceptions from dividing by zero in integer mode. You really want your calculator to do the full range of numbers, not just integers, so why don't you change the result and input variables (back) to double, and we'll work on getting the input to come in as a full floating-point value.

I Googled (something like) "Java input double from keyboard" and got the usual full page of hits on how to do it. Most of them said you do it with the Java Scanner class. Then I Googled "Java Scanner class" and the top hit (Google changes up their search results from time to time, so it might be only near the top when you do it) gave the "docs.oracle.com" website's complete specification of the Scanner class. You should bring it up and read through it at least once, so you get a feel for what you can find on the official documentation page. Notice that they are a little thin on how to use it. That's what StackOverflow and the other Java help/tutorial websites are for. Take some time to read some of them and see if you understand it well enough to write your own code here in your calculator program. Then resume reading here and I'll try to explain the same ideas simpler and more directed to your calculator program.

(Waiting for you to read the other websites...)

In these examples, the Java Scanner class is usually constructed with a file as a parameter -- technically it's a Stream, which also includes anything that pretends to be a file, like a unix pipe -- but for our purposes it's the standard keyboard input System.in. If you looked inside my Zystem class (if you have not already, this might be a good time to do it), you saw that all of my input ("ReadWhatever") functions read characters from that file. Reading from a file might throw exceptions, so I always wrap a "try...catch" around it. Today you will be doing it too.

Anyway, the point of a scanner -- that's a technical word, meaning it scans input text and extracts out tokens by recognizing how they are spelled, for example in Java, if it starts with a digit, it's a number; if a number has a decimal point or the letter 'e' inside, it's a floating point number (double) and so on -- so the scanner's job is to pick out (and usually convert to an appropriate native machine type) the different kinds of things you might want to find in a file. Usually we need this most for converting text input into the numbers that we humans can read just by glancing at it, but in our case today, we want it also to see an operator character, one of the four mathematical operators ( + - * / ) so our calculator knows what to do with the number you give it.

Java library routines try to do as much as possible, so there are a lot of Scanner methods that you will just ignore. First you need to decide what you expect your user to type in. It is the nature of Java functions (methods that return a value) that you need to know in advance what the data type of the value is, so basically you tell it what you want, and if that's not what it sees there, it throws an exception. Making Java programs user-friendly is a lot of work, as you will see.

Most people are familiar with 4-function calculators where you push an operator button, and then you push digits (possibly including a decimal point), and then repeat, or else push '=' to get the final result, or else an intermediate result before continuing. You could do that, but the Java console would make it harder than you'd think. Later, after you have learned the GameEngine, you might try to do it in that environment, where you can get keystrokes as individual events.

For now you might consider requiring your user to type an operator followed by a space followed by a number and a return ("Enter" key), or just an operator and a return (if the operator expects no number, like Clear or Equal). This will work in the Scanner using (A) just two methods, next() to get the operator, and then nextDouble() to get the number. Java will hold off your input until the user types the return, but the scanner will pick off the tokens (one word at a time) ignoring the line ends.

Alternatively, you could (B) use nextLine() to get the whole input line as a String, then pick off your operator using a suitable string method, then pass the rest to another instance of the Scanner class to extract the number from it. Easier than using another instance of Scanner -- perhaps you saw this in your Google search (you did try that, right? You need to, you will be doing this a lot) -- you can (C) use Double.parseDouble(str) to convert the remaining string to double.

Or you could (D) use Zystem.ReadLetter() to read the operator and Zystem.ReadWord() to get the number string, then use Scanner or parseDouble to convert it to double. Or you could (E) take the easy way out and insist that your users can only enter whole numbers, and use Zystem.ReadInt() to read the number as an integer. Oh wait, we already did that.

Let's do them each (more or less) in order...

A. nextDouble

I haven't tried it, but I doubt Scanner will play nice with Zystem for shared use of the keyboard input, so we need to use the Scanner class for both the operator input and the number. The first thing to do is (before the while) instantiate an object of type Scanner:
Scanner scn = new Scanner(System.in);
while (true) {

Scanner does not include a method for reading just a single non-blank character (type char), so reading it will take two steps. First we read a (non-blank) word, then we pick out the first character of that word. I called my String variable aWord:

/// b. accept a command letter:
aWord = scn.next();
optor = aWord.charAt(0);

Then when we are ready to read in the input value,

/// e. accept a value
inval = scn.nextDouble();

You did change your input and result variables (back) to double, right? Java will complain if you try to put a value of type double into a variable of type int. Automatic promotion only goes toward a wider or more general format, never the other way.

What happens when you try to compile this? Besides type faults (if you forgot). Scanner is a standard Java library class, but you must ask for it (like Random, back when we were doing rock-paper-scissors). One of the sites I found when searching Google for how to input double, began their example with the line

import java.util.Scanner;  // Import the Scanner class
Not all of them did. If you are learning a new class, and none of the help sites bothered to tell you how to spell the import line -- StackOverflow is usually very helpful, but they almost never tell you about the import -- you can look in the documentation page for a line that looks something like this:
where Classname is whatever class you are working on, and something is the part you don't know: in our case it's ".util." (same as Random) but other classes can have a different package name, or sometimes even multiple names there in the middle. If you are getting a library class from somewhere other than Java, it would have a different name in front too, often the domain name of whoever is making it available. Don't forget, all import lines go at the beginning (before the class name) of whatever code file you are using that imported class in.

Now you should be able to compile and run your calculator program. Did it work OK? Don't forget that the input line now requires a space between the operator and the value. When I tried messing with the input line, sometimes the program got very confused, I think the fault of Java (we'll fix that in the Game Engine), and sometimes it crashed with an exception. Oh wait, you already have a try-catch inside your while-loop, right? If you type in random letters instead of a valid number, you get a new exception "java.util.InputMismatchException" or something like that. If your program caught it, then it's still running, you have control, you can do something reasonable. For paid-up users, just printing out the exception name is not reasonable, they cannot be expected to know what it means. Instead, your code should attempt to figure out (inside the catch-clause braces) what went wrong, and what the user should do about it -- probably in this case, retype their input -- and tell them so politely (beep is not polite, nobody but you knows what it means).

Catching Different Exceptions

Did you notice, the exception name is in the form of a full class name (starting with "java.util." just like the Scanner class), which you could import at the front of your program. Why would you want to do that? Suppose you add a new catch-clause to accept only that one type of exception:
} // end of try
catch (InputMismatchException ex) {
  System.out.println("Invalid input, please try again");
} // end of InMmEx catch
catch (Exception ex) {
  System.out.println("Unexpected error " + ex.toString());
} // end of default catch
Then you could know exactly what kind of exception you got (because this is all this particular catch will see), and you can give the user an informed error message. Ideally you would know about all possible exceptions -- they each have a separate class name -- with their own catch clauses, and keep the default to catch any surprises (and fix them before you release the code). The documentation for each class you use should list all the possible exceptions the methods in that class might throw. For example, the Scanner class constructor -- that's the "new Scanner(System.in)" line -- might throw an exception if the input file has a problem. As written, our Calculator program would just crash, because there is no try-catch containing that constructor. You need to watch out for these things when you want to write robust (never-fail) software.

It's probably a good idea if you try each of the other input ideas, especially if you want to know this like a professional. Otherwise skip forward to the next page and start learning about OOPS.

C. parseDouble

You might use this way of doing things if you want to give the user the option of different types of data, which you wouldn't know until you got it in and looked at it. So you would read the data in as a String, and then use one of the built-in conversion routines (in this case Double.parseDouble) to convert the string to a number:
/// e. accept a value
aWord = scn.next();
inval = Double.parseDouble(aWord);
This will have its own exceptions, which you should check for.

B. nextLine

This one is messier, because you would read in the whole line, then pick out and delete (using String tools, see the "String Tools" section in Things You Need to Know) the first non-blank character to be the operator, then pick out the number part of what's left (removing extra spaces) before passing it to parseDouble. It takes a couple additional variables, String aLine to hold the whole line, and int here to find the parts.  Here's the input:
/// b. accept a command letter:
aLine = scn.nextLine();
aWord = aLine.trim(); // removes spaces at the front (and back)
optor = aWord.charAt(0);
here = aLine.indexOf(aWord);
aWord = aLine.substring(here+aWord.length()); // removes operator from aLine

Then when we are ready to read in the input value,

/// e. accept a value
inval = Double.parseDouble(aWord.trim());

All of these string operations have failure modes (they will throw exceptions) which you need to (a) read up on, and then (b) add remedial code to deal with them properly. This will run without that remedial code, but the program will crash or (if you have a generic catch inside the while) possibly ignore the user input without adequate notification. As they tell you in school, "That is left as an exercise for the student."

Next: Take a short survey before continuing with Objects

<<Previous | ToC | Next >>

Revised: 2023 February 27