JavaRanch Home    
 
This page:         last edited 16 February 2014         What's Changed?         Edit

User Input   

(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:

1. Ask them to enter a value.
2. Read the value they enter.
3. Validate it in some way (more on that later).  
4. If there's a problem:  
  4.1. Display a message telling them they're an idiot.
  4.2. Go back to Step 1.

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:

System.out.println("Please enter a number");
int number = myScanner.nextInt();

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:

System.out.println("Please enter a number");
int number;
try {
  number = myScanner.nextInt();
} catch (InputMismatchException e) {
  // now what?
}

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:

System.out.println("Please enter a number");
int number;
while(true) { // = do forever
  try {
    number = myScanner.nextInt();
    break; // breaks out of the loop
  } catch (InputMismatchException e) {
    System.out.println("I said a NUMBER, you idiot");
  }
  System.out.println("Please enter a number");
}
// next statement

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:

So:

String input = myScanner.nextLine();
int number = Integer.parseInt(input);

accomplishes pretty much exactly the same thing as:

int number = myScanner.nextInt();

.

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:

String trimmed = myScanner.nextLine().trim();
int number = Integer.parseInt(trimmed);

and putting that in our previous loop we get:

System.out.println("Please enter a number");
int number;
while(true) {
  try {
    String trimmed = myScanner.nextLine().trim();
    number = Integer.parseInt(trimmed);
    break;
  } catch (NumberFormatException e) {
    System.out.println("I said a NUMBER, you idiot");
  }
  System.out.println("Please enter a number");
}

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:

public static final String input(
  Scanner s, String prompt)
{
  System.out.println(prompt);
  return s.nextLine().trim();
}

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:

private static final Integer integerOrNull(
  Scanner s, String prompt)
{
  try {
    String input = input(s, prompt);
    return Integer.valueOf(input);
  } catch (NumberFormatException e) {
    return null; // NOTE THE DIFFERENCE
  }
}

which can be shortened to:

private static final Integer integerOrNull(
  Scanner s, String prompt)
{
  try {
    return Integer.valueOf( input(s, prompt) );
  } catch (NumberFormatException e) {
    return null;
  }
}

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:

Integer number = integerOrNull(myScanner,
  "Please enter a number");
while(number == null) {
  System.out.println("I said a NUMBER, you idiot");
  number = integerOrNull(myScanner,
    "Please enter a number");
}

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 :

public static final Integer getInteger(
  Scanner s, String suffix)
{
  String prompt = "Please enter " + suffix;

  Integer i = integerOrNull(s, prompt);

  while(i == null) {
    System.out.println(
      "Invalid number. Please try again.");
    i = integerOrNull(s, prompt);
  }

  return i;
}

which you would then call with something like:

int number = getInteger(myScanner, "a number");

in your main() method. And just in case you're worried: an Integer will be "boxed" to an int, so there's absolutely nothing wrong with the call.

.

Just FYI, we can remove the duplicate integerOrNull() call by making it part of the loop test, viz:

public static final Integer getInteger(
  Scanner s, String suffix)
{
  String prompt = "Please enter " + suffix;
  Integer i;

  while(( i = integerOrNull(s, prompt) ) == null) {  
    System.out.println(
      "Invalid number. Please try again.");
  }

  return i;
}

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:

Please enter a number

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:

Invalid number. Please try again.

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:

int number  = getInteger(myScanner, "a number");
int number2 = getInteger(myScanner, "another number");
int number3 = getInteger(myScanner, "a third number");
etc...

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:

private static final Double doubleOrNull(
  Scanner s, String prompt)
{
  try {
    return Double.valueOf( input(s, prompt) );
  } catch (NumberFormatException e) {
    return null;
  }
}

public static final Double getDouble(
  Scanner s, String suffix)
{
  String prompt = "Please enter " + suffix;
  Double d;

  while((d = doubleOrNull(s, prompt)) == null) {
    System.out.println(
      "Invalid decimal number. Please try again.");
  }

  return d;
}

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:

public static final int getMonth(Scanner s) {
  int month = getInteger(s,
    "a month between 1 and 12");

  while (month < 1 || month > 12) {
    System.out.println(
      "I said between 1 and 12, moron");
    month = getInteger(s,
      "a month between 1 and 12");
  }

  return month;
}

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:

  1. Any old number - which we've already seen.
  2. 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:

public static final int getInRange(
  Scanner s, String name, int lowest, int highest)
{
  String range = "between " + lowest + " and "
    + highest;
  String suffix = name + " " + range;

  int inRange = getInteger(s, suffix);

  while (inRange < lowest || inRange > highest) {
    System.out.println("Out of bounds: must be "
     + range);
    inRange = getInteger(s, suffix);
  }

  return inRange;
}

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:

int month = getInRange(myScanner, "a Month", 1, 12);

and for an age:

int age = getInRange(myScanner, "an age", 0, 125);

and a salary:

int salary = getInRange(myScanner, "a salary", 
  10000, 125000);

And just in case you didn't work it out, the prompt for that last call will be:

Please enter a salary between 10000 and 125000

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):

public class myClass {  
  ...

  public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);

    int number = getInteger(scanner, "a number");

    int month  = getInRange(scanner, "a Month", 1, 12);
    int age    = getInRange(scanner, "an age", 0, 125);
    int salary = getInRange(scanner, "a salary",
      10000, 125000);
    ...
  }

  public static final String input(Scanner s, String prompt)
  {
    System.out.println(prompt);
    return s.nextLine().trim();
  }

  private static final Integer integerOrNull(
    Scanner s, String prompt)
  {
    try {
      return Integer.valueOf( input(s, prompt) );
    } catch (NumberFormatException e) {
      return null;
    }
  }

  public static final Integer getInteger(
    Scanner s, String suffix)
  {
    String prompt = "Please enter " + suffix;
    Integer i;

    while((i = integerOrNull(s, prompt)) == null) {
     System.out.println(
      "Invalid number. Please try again.");
     i = integerOrNull(s, prompt);
    }

    return i;
  }

  public static final int getInRange(
    Scanner s, String name, int lowest, int highest)
  {
    String range = "between " + lowest + " and "
      + highest;
    String suffix = name + " " + range;

    int inRange = getInteger(s, suffix);

    while (inRange < lowest || inRange > highest) {
     System.out.println("Out of bounds: must be "
      + range);
     inRange = getInteger(s, suffix);
    }

    return inRange;
  }
}



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:

public static final boolean getBoolean(Scanner s,
  String question, String yes, String no)
{
  String prompt = question + " Enter '"
    + yes + "' for yes, '" + no "' for no";

  while(true) {
    String input = input(s, prompt);

    if (input.equals(yes))
      return true;
    else if (input.equals(no))
      return false;
    else System.out.println("Input is not '"
      + yes + "' or '" + no "'");
  }
}

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:

boolean continue = getBoolean(myScanner,
  "Do you want to continue?", "y", "n");

and its prompt would then be:

Do you want to continue? Enter 'y' for yes, 'n' for no

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:

public static final Calendar getDate(Scanner s) {
  int yy = getInRange(s, "a year", 1900, 2050);

  // Determine if the year they entered is a leap-year
  boolean leapYear = yy % 4 != 0 ? false
    : yy % 100 != 0 ? true : yy % 400 == 0;

  int mm = getInRange(s, "a month", 1, 12);

  // Work out the length of the month they entered
  int monthLength;
  switch (mm) {
    case 2:
      monthLength == leapYear ? 29 : 28;
      break;
    case 4:
    case 6:
    case 9:
    case 11:
      monthLength == 30;
      break;
    default:
      monthLength == 31;
  }

  int dd = getInRange(s, "a day", 1, monthLength);

  return new GregorianCalendar(yy, mm - 1, dd);
}

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:

  1. Put each type of input in a separate method.
  2. Use Scanner.nextLine() to get your basic input; or, better still, an input() method, as shown above.
  3. 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.
  4. Supply, at least, a Scanner and a prompt to each method.
  5. Let the looping method deal with the business of creating proper instructions and error messages for the user.
  6. (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:

  1. One or two letters.
  2. A digit (0-9).
  3. An optional letter OR digit.
  4. A space.
  5. A digit.
  6. 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:

str.matches("[A-Z][A-Z]?[0-9][0-9A-Z]? [0-9][A-Z][A-Z]")

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:

public static final String getPostalCode(Scanner s) {
  String prompt = "Please enter a postal code";
  String postalCodePattern = 
    "[A-Z][A-Z]?[0-9][0-9A-Z]? [0-9][A-Z][A-Z]";

  while(true) {
    String input = input(s, prompt).toUpperCase();

    if (input.matches(postalCodePattern))
      return input;

    System.out.println("Input is not a valid postal code");
  }
}

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

JavaRanchContact us — Copyright © 1998-2014 Paul Wheaton