(Level: beginner)
One of the first things you'll need to do when you're learning
Java is to write lots of programs that take input from a user and store it in your program.
Unfortunately, many beginners just start banging away with
Scanner (more often than not, in
main()), only to discover when they're about halfway through - and after they've written a LOT of code - just what a damn fiddly process user input is.
This page is designed to help you to write this stuff
properly, and it assumes that your program is console-based (ie, you're running it from the command line), and it
doesn't use Swing or web pages for display - or at least not until
after you've got your input.
It takes you through the process step by step, so it's fairly long. Don't worry; the final result is quite straightforward. It's also in two parts: in the
second, we'll go into the business of writing your own utility class to do this, so you don't have to keep repeating the same code.
.
Lesson 1:
User input is
tough. It's finicky, it's fiddly, and you need to deal with all sorts of things that can possibly go wrong - not the least of which is that your user is a complete
idiot, and doesn't follow instructions.
The general
pattern for
each value that a user needs to enter goes something like this:
Unfortunately, most books/classes simply show you the basics of Java's
Scanner class and leave you to get on with it; and it's NOT simple.
First: You're dealing with a complex process, so you need to
design it.
Second:
Scanner is
not a simple class - although it's a lot simpler than many of the alternatives - and it has several idiosyncrasies and "gotchas" that you need to know about in order to use it properly.
My strategy will be simple: I'm going to teach you how to
avoid them, so that you can get on with the business of
getting input from your user.
KICKOFF - Getting an Integer:
So, you need a number from your user.
The first thing you need to understand is that the keyboard is a
character device, so, whenever you press a key, it returns a character, not a number - even if that character is '2'.
Characters are NOT the same thing as numbers, so in order to get an
integer (ie, an
int or an
Integer), you need to
convert the character - or, more likely, the
string of characters - entered by the user.
.
Let's suppose, just for a moment, that you've discovered that
Scanner.nextInt() can take input from the keyboard and return you an integer, so you write:
Now normally, this will work just fine; but today but your idiot user decides to type in "X" instead of a proper number. What happens?
Answer: Your program fails with an Exception. Specifically: an
InputMismatchException.
Do you really want that to happen? If you're happy with that behaviour, then your job is done and you can skip the rest of this tutorial.
Seriously.
Never write code you don't have to.
.
But might it not be better to include that
step 4 logic you saw above? - ie, tell them they're an idiot and keep prompting for input until they get it right.
And
this is where the problems start (and basically what this tutorial is all about).
In order to do that, you need to know if the method you just called threw an Exception or not; and for that you need a
try...catch block.
So you write:
What do you do? You can't use the value of your
int field, because any value you choose to indicate an error might be a perfectly valid number that they actually typed in correctly. Furthermore, we haven't yet included any looping mechanism that forces them to enter it again until they get it right.
So, lets do that:
Now
that will work. It's crude, but it will work because the loop will just keep cycling until they enter something that
doesn't cause an InputMismatchException; so by the time you get to "next statement" you'll
know that they entered a valid number.
.
But
good grief, it's a lot of code - and it's ugly too. And you have to do it (or something like it) for
every single value you ask your user to enter.
Suppose your program needs to collect TEN numbers from the user; what do you do then? Repeat all that code 10 times? BAD IDEA.
Put it in a loop? Possibly; but what if you need different prompts for each number? What if some of them need to be validated further? For example: what if
one of them is a Month, so you need to make sure your idiot user enters a value between 1 and 12. You could certainly add logic to do it, but you
couldn't put it in the loop - and besides, it's just
more code.
And so far, we've only dealt with
integers. What about decimal numbers (eg, 1.25), or booleans (eg, YES/NO), or zip codes? There are a whole bunch of things we might need to ask for, and even more ways to validate it.
.
At this point, I usually advise programmers to
StopCoding (click it).
We need some way to
rationalise this process, rather than just adding scads and scads of code.
So, what's wrong?
First: We're using Scanner's conversion methods, which are brittle because they lump the process of scanning and conversion together. They also have other idiosyncrasies, such as missing out newlines, which nobody warns you about until you run into them.
So my advice (and you won't find it in too many books):
forget them.
There is only
one method (other than
close()) that you need to know about when using a Scanner:
nextLine().
This takes whatever characters a user enters until they hit the ENTER key, and returns them to you as a Java
String. No validation, and no errors other than ones that your program can't possibly do anything about.
So what you get is a
String. ALWAYS.
And
now you can worry about validation and/or conversion.
Furthermore, the method doesn't return
until the user presses the ENTER key,
and it consumes the newline character; so you
know that your Scanner is always in a state to accept another line of input.
CAVEAT: Not everybody will agree with the above advice. It's my opinion, based on several years of dealing with Scanner, and it follows a very old programming principle:
Keep things simple.
I should add that it hasn't steered me wrong ... yet
.
.
Second:
We need to normalise our input process by putting in a
method. In fact, probably more than one.
.
Third: We need to analyse the sorts of thing we might need to do when a user enters a value, so that we don't need to write a new method for each new input.
Integer input, revisited:
So, how do we turn a String into an
int (or indeed - possibly better - an
Integer)?
A quick look at the
Integer class docs will tell you how:
For an int, you use: parseInt(String).For an Integer, you use: valueOf(String).
So:
accomplishes pretty much
exactly the same thing as:
.
The only thing I would suggest is that you add a call to
String.trim() to make sure any extra spaces your idiot user might have inadvertently typed in at the start or end of the line are removed. So you get:
and putting that in our previous loop we get:
Also: note the different Exception:
parseInt() throws
NumberFormatException, not
InputMismatchException.
.
However, our loop is now
even bigger, so let's have a look at that
second point we mentioned...
Putting it in methods:
OK, now we now have a loop we can use, but it's still
awfully cumbersome. 12 lines of code to deal with
one value? How do we rationalise it?
First: we can put all that code, which we would otherwise have to repeat for each value, into a method (actually, methods, as you'll see below).Second: we can use objects instead of primitives.Third: we can split up the input from the looping.
Why objects? Because they have one great property that we can use to our advantage:
they can have a value of null; and we can use
that to know when there's been a error.
So: lets have a look at the input part of our loop (
just the input for now), and see if we can't break it up a bit and put it into methods.
.
First: We need to actually get some input from a user and trim it; and when we do, we will almost certainly want to display a prompt message first. So let's do it:
Simple, eh? The method displays the prompt message we pass to it and returns the String they enter, trimmed of any whitespace at the start and end.
.
Second: We need to convert that String to a number - which is where we may get the Exception. For the moment, our remedy is to simply return
null if there's a problem:
which can be shortened to:
Note that we've made this method
private because it's really only for
our use; and also note the use of
valueOf() because we're returning an
Integer now.
And
because we're returning an
Integer, which is an
object, we can return
null if there's a problem. We
can't do that if we return an
int.
NOTE: This is one of the
very few cases where I advise returning
null. In general, you should avoid them like the plague, but in this particular case it provides a very useful piece of information ("something went wrong with the conversion") that we can't get easily any other way.
.
Now our loop becomes slightly different:
Much smaller - however, it's
still a lot of code for our
main() method, so why not put
it in a method too?
.
Let's take a first cut at it. And while we're at it, let's tone down those messages a bit - it's not a great idea to be
too rude to your users, no matter how tempting it might be
:
which you would then call with something like:
in your
main() method. And just in case you're worried: an
Integer will be "unboxed" to an
int, so there's absolutely nothing wrong with the assignment.
.
Just FYI, we can remove the duplicate
integerOrNull() call by making it part of the loop test, viz:
however, if you do so, you MUST remember to put the assignment inside brackets, because its operator (
'=') has a
lower precedence than the equality operator (
'==').
.
I have to admit to a slight preference for the second style, so I will be using it for the rest of this tutorial; but be aware that some senior programmers really
don't like "compound" expressions like this. And if you prefer the first, which is arguably a bit clearer, feel free to use it instead.
Did you also notice that we're now supplying a
'suffix'? The
"Please enter " is redundant, because we'll probably want to prefix pretty much every request for input with it (or something similar), so we might as well just put it in the method.
And that's
another thing that makes user input so verbose: all those darn prompt and error messages you have to write. And I hate to say, but there's not much you can do about it.
Just to make sure you follow, let's go through the process in detail:
getInteger() calls
integerOrNull(), which calls
input(), which first displays the prompt:
and then waits for the user to type in some characters
and press the ENTER key (very important); returning whatever they typed in to
integerOrNull() as a
String.
If they entered something invalid, then
valueOf() will throw an Exception, causing
integerOrNull() to return
null, which in turn causes
getInteger() to enter the
while loop. The first thing it does there is to display the error message:
and then call
integerOrNull() again, which calls
input() again...
However, as soon as the user gets it
right,
integerOrNull() will return something
other than
null, which causes
getInteger() to break out of the loop (or never enter it, if they get it right first time) and return the converted value.
Now that probably sounds like an awful lot of stuff, but essentially, it's
exactly the same process we were doing before in our loop; we've just broken it up into discrete tasks. And, most important of all,
we've removed it from main().
Make sure you follow it before you continue. You may also find it helpful to look at the
class listing, where everything is shown together.
.
Now it may seem as though we've just added a lot more code; but just look at what happens when your idiot user needs to enter 10 values:
10 lines instead of
130.
This is how you design programs: By reusing code.
Note that the methods
must be
static because we're calling them from
main(), which is itself
static; and static methods should almost invariably be
final as well.
There's also no real reason to make them anything else, because they're self-contained - ie, they don't rely on any
instance values to do their jobs.
.
We can also use this pattern for any other type of number, viz:
And that probably covers the major types of number you'll need for now.
It's probably also worth adding that the problem of conversion Exceptions is peculiar to
numbers. For other input, we usually don't have to worry about it.
.
But hold on hoss, we're not done yet. Not by a
long chalk...
So...what's wrong NOW?
We now have a method that can return a number input by a user, but what if we need our user to enter a Month (ie, a number between 1 and 12)?
Sure, we could write something like this:
But then we'd need a different method for a month number, or a week number, or somebody's age, or salary, or test score... the possibilities are almost endless.
Isn't there some way we can make this more generic?
Well as it turns out, there is; but it takes a bit of
thought. And this is something you should get into the habit of doing. ALWAYS.
Think before you start coding.
The fact is that almost all requests for numbers fall into two basic categories:
Any old number - which we've already seen.A number within a range - as with our month example.
There is a third - a number from a specific set of "valid numbers" - but that's beyond the scope of this tutorial.
.
So lets write a more generic method that takes a range:
Note that we didn't use a "compound" expression as we did
above, because the loop
test is itself a compound expression, which would make the whole thing very cumbersome. This is the point at which clarity should win out over brevity.
.
And
now, to get a month, we can get rid of our
getMonth() method altogether and just call:
and for an age:
and a salary:
And just in case you didn't work it out, the prompt for that last call will be:
see if you can work out why.
.
It's still quite a lot of code, but nowhere near as much as we'd have to write if we dealt with each value individually. And it's just a basic fact of life:
User input is fiddly.
Do you also follow the process? We've built up code slowly, as we find a need, adjusting things along the way if we have to; and now, with four methods, we can deal with pretty much any integer input we're ever likely to want.
Also: notice that for each new level of validation, we need to write
another loop; just another reason why user input is so darn complex.
.
And, if you fancy a little exercise after all this reading:
See if you can use what you now know (including any of the methods you've already seen) to write a method that
forces the user to enter a
prime number between 1 and 100.
(Tip: You'll first need to know how to check if what they enter - assuming it's a valid number -
is prime or not)
I'm afraid we won't be able to check your efforts, but good luck if you decide to try.
Our class so far
Just to recap, our class will now probably look something like this (I've only the included the methods needed for integer input for illustration):
Other types
So far, we've only dealt with integers; what about the other types we mentioned earlier?
Well, the general pattern is the same: Use
nextLine() to get the input (or indeed, the
input() method we just wrote) and then validate it and, if necessary,
convert it to the type in question.
We already showed you what the methods for a Double would look like, and its "
getInRange()" method will be virtually identical to the one we wrote for integers, except that
lowest and
highest will be
doubles.
I don't propose to go through all numeric types (there are 8) - suffice to say they will all follow the same basic pattern and, to be honest, you probably don't need them all. But feel free to try a couple out for practise.
.
Instead, let's have a look at
boolean:
Note that there is no need for a separate conversion method because we're not calling anything that throws an Exception, so we don't need a
try...
catch block.
Also notice the different nature of the prompt: Booleans are usually the result of a "yes/no"-style question.
So a call to this method in
main() might look something like:
and its prompt would then be:
Also notice that we can return the primitive, because we don't need a
null value.
.
We can also
combine the methods we've just written to input other more complex items. For example:
See how easy it is? And no annoying loops this time, because our methods already take care of that.
.
There are simply too many types to go into exhaustively, but you can make life a lot easier for yourself if you follow some of the rules you've already seen:
Put each type of input in a separate method.Use Scanner.nextLine() to get your basic input; or, better still, an input() method, as shown above.If the input needs conversion that can throw an Exception (as in the case of numbers), put the conversion in a separate private method that returns null if there's any problem; then write a second method that loops while the returned value is null.Supply, at least, a Scanner and a prompt to each method.Let the looping method deal with the business of creating proper instructions and error messages for the user.(MOST important) Don't let the looping method return anything until you know that the user has entered valid data.
.
And before you go rushing off to copy down all the methods you've seen:
they are only examples, and there are
many ways to write them. It's the
pattern that's important, not the code. You may well have other things that you need to check for, so it's important that you've understood the
process, not memorized the code.
That said, I suggest you stick pretty close to what you've seen for the
input() and
<type>OrNull() methods - at least for now.
Object-Orientation
So, we've saved ourself a lot of code (although it might not seem like it at the moment); but do we really have to write all this stuff in
every program we write?
Well, considering that you were probably originally going to write it ALL out in longhand,
in main(), you should be darn pleased that you've got this far; but the simple answer is:
Of course not.
But in order to do that, you need to
think some more.
.
Java is an
Object-Oriented language, and that means we get
objects to do our work for us. And since these methods are always going to be pretty much the same, we might as well put them in a single class
designed specifically for the job.
But that's the subject of
UserInputPartDeux.
For the moment, re-read this one and make sure you understand it. I also suggest that you try out some of the methods for yourself and test them.
Patterns
Another thing you may well have to do is match an input string against a pattern. We have
tons of the darn things these days: bank codes, zip codes, Social Security numbers, telephone numbers, license plates... the list is endless.
I've left this section till last because, unfortunately, it involves a bit of knowledge about
Regular Expressions. Only a bit; but if you haven't run across them yet, you can skip it for now and go onto the
second part of this tutorial.
The basic procedure for patterns is the same as you've seen before - get the input, validate it, and keep telling the user they're an moron until they get it right - it's the 'validation' bit that's slightly different.
Let's take as an example, a British postal code. It has a
very specific format. Specifically, and
in sequence:
One or two letters.A digit (0-9).An optional letter OR digit.A space.A digit.Two letters.
Now you
could write a method that checks each character in turn to make sure it's correct, but it would be extraordinarily tedious and error-prone. Luckily, there's a wonderful method in the String class called
matches(), which does all this for you very easily. If you're not familiar with it, I suggest you read the link.
Suffice to say that the following statement will do a complete UK postal code validation for you:
I don't propose to explain how it works; just trust me: it does. If you want to find out more about regular expressions, click on the link above, or there are many good resources on the Net.
.
So, how do we use it? Let's have a look:
What? Did you think it was going to be mind-blowingly complex? We don't even need to pass a prompt because the
method defines what the message is going to be; and since we're returning a String, there are no fancy conversions that need to be done.
About the only thing to note is the
toUpperCase() call. UK postal podes mandate the use of CAPITAL letters, so just in case our idiot user forgot to press the Shift key, we might as well convert what he gave us to upper case ourselves and save a lot of hassle.
.
And now you've come to the end of this part of the tutorial. Make sure you understand it, and I also suggest that you try out some stuff for yourself before you go onto
UserInputPartDeux.
CategoryWinston