Using Java's Scanner and Console Class to Read Input


This example has three main purposes:

  1. it shows how you can read input from stdin using Java's Scanner class,
  2. it shows how you can use the Scanner class to type check the user's input,
  3. it shows you Java's Console class


The Scanner Class

The Scanner class must be imported from java.util. It provides a wrapper class that encapsulates an input stream, such as stdin, and it provides a number of convenience methods for reading lines and then breaking a line into tokens. This set of notes will cover the set of Scanner methods that you will use most frequently. You can look at the Scanner's API to get the full list of methods that a Scanner provides.


The Constructor

The constructor for the Scanner class takes a Java InputStream, File, or String as a parameter and creates a Scanner object. Basically the Scanner class works with anything that supports an iterator, since what you are essentially doing is iterating through a collection of tokens.

In Java, the variable System.in is declared as an InputStream and it points to stdin. System.in is a byte stream so you can't read from it directly if you want to read character strings, which is what you normally want to do. Hence you must wrap a Scanner object around System.in to handle string oriented IO. The following statement accomplishes this task:

Scanner console = new Scanner(System.in);
If you want to treat System.in directly as a character stream, use the Console class instead, which is discussed below.

Oftentimes you will want to read a line of user input and then break it into typed fields. The Scanner class allows you to easily break the line into tokens, and even type check the tokens, by creating a Scanner class that tokenizes the line. For example, if you have a line of user input in the String variable nextLine, then you can create a Scanner that will tokenize the line using the following statement:

Scanner lineTokenizer = new Scanner(nextLine);

Reading a Line of Input


Reading and Type Checking Tokens

A Scanner object will divide a line of input into tokens (fields) using white space to delimit the tokens. For example, given the line:

brad 10 true
a Scanner will create the tokens "brad", "10", and "true". You can retrieve these tokens and convert them to the appropriate types using the following set of methods (see the API for a complete set of next methods): If the input has been exhausted or if the token cannot be converted to the desired type, then the above methods will throw exceptions. To avoid having exceptions thrown, you can check whether or not a token exists and whether or not it conforms to the desired type using the following set of methods (see the API for a complete set of type checking methods):


Discarding a Scanner

When you have finished using a Scanner, such as when you have exhausted a line of input or reached the end of a file, you should close the Scanner using the close method. If you do not close the Scanner then Java will not garbage collect the Scanner object and you will have a memory leak in your program:

Because it is easy to forget to close a Scanner, a better option is to use a so-called "try with resources" statement. Here is an example:

try (Scanner lineTokenizer = new Scanner(inputLine)) {
    int age = inputLine.nextInt();
    System.out.printf("age = %d%n", age);
}
    
The try with resources statement will automatically close the resource when the try block exits.


An Example

To illustrate the use of a Scanner, consider the following problem:

Read and type check lines of user input. Each line should contain three fields organized as:

name(String) age(int) single(boolean)
The types of the fields are shown in parentheses. Errors should be reported using an appropriate error message. Valid fields should be consumed and the Scanner should move on.

Here is a sample input file:

brad 10 true
nels 10
nels 10 false
nels 10 10
brad
10 20 30
10 true 40

brad nels 
and here is the output that the program should produce:
line 2: must have a field for singleness
line 4 - 10: singleness should be a boolean
line 5: must have fields for age and singleness
line 6 - 30: singleness should be a boolean
line 7 - true: age should be an integer
line 7 - 40: singleness should be a boolean
Line 8: line must have the format 'name age singleness'
line 9 - nels: age should be an integer
line 9: must have a field for singleness

Here is the complete program for accomplishing this task

import java.util.Scanner;

class DataCheck {
  static public void main(String args[]) {
    new DataCheck(args);
  }

  public DataCheck(String args[]) {
    Scanner console = new Scanner(System.in);
    int lineNum = 0;

    while (console.hasNextLine()) {
      // The following try is a so-called "try block with resources". It automatically
      // closes the resource when the try block exits. I have used continue
      // statements in this loop and the try block guarantees that the
      // lineTokenizer scanner will be closed even when a continue statement
      // is executed.
      try (Scanner lineTokenizer = new Scanner(console.nextLine())) {
	lineNum++;
	// determine if the line has a name field
	if (lineTokenizer.hasNext()) {
	  lineTokenizer.next();  // consume the valid token
	}
	else {
	  System.out.printf("Line %d: line must have the format 'name age singleness'%n", lineNum);
	  continue;  // proceed to the next line of input
	}
	// determine if the line has a second field, and if so, whether that
	// field is an integer
	if (lineTokenizer.hasNext()) {
	  if (lineTokenizer.hasNextInt()) {
	    lineTokenizer.nextInt(); // consume the valid token
	  }
	  else
	    System.out.printf("line %d - %s: age should be an integer%n", 
		lineNum, lineTokenizer.next());
	}
	else {
	  System.out.printf("line %d: must have fields for age and singleness\n",
	      lineNum);
	  continue; // proceed to the next line of input
	}
	// determine if the line has a third field, and if so, whether that
	// field is a boolean
	if (lineTokenizer.hasNext()) {
	  if (lineTokenizer.hasNextBoolean())
	    lineTokenizer.nextBoolean(); // consume the valid token
	  else {
	    System.out.printf("line %d - %s: singleness should be a boolean\n", 
		lineNum, lineTokenizer.next());
	    continue; // proceed to the next line of input
	  }
	}
	else {
	  System.out.printf("line %d: must have a field for singleness\n", lineNum);
	  continue; // proceed to the next line of input
	}
      }
    }
  }
}

The Console Class

An easier way to deal with stdio is to use the Console class, which supports both reading from stdin and writing to stdout. It reads character strings so you do not have to worry about wrapping System.in inside a Scanner class. You use the readLine method to read a line of input, and a Scanner class to tokenize the line of input. readline returns null on end of file.

You can obtain the system provide console object from System.console. It is a good idea to check whether this object exists, since it does not exist for non-interactive Java programs, which includes program in which stdin is redirected. For example, the following invocation will not have a console object:

java foo < inputfile   // no console object for programs with redirected input
A console object also may not exist for Java programs run with older versions of the Java virtual machine and some systems will not provide it for security purposes. In all of these cases you will have to fall back on System.in and System.out.

Here is the same example code from above, except using the console class. I have boldfaced the new code:

import java.util.Scanner;
import java.io.Console;  // Console is in the java.io library

class Datacheck {
  static public void main(String args[]) {
    new Datacheck(args);
  }

  public Datacheck(String args[]) {
    String inputLine;
    int lineNum = 0;
    Console inputReader = System.console();
    // I'm just going to exit if the console is not provided
    if (inputReader == null) {
      System.err.println("No console.");
      System.exit(1);
    }
    // you cannot use the C/C++ idiom of
    //   while (inputLine = inputReader.readLine())
    // because the test expects a boolean and the above assignment returns
    // a String reference. In C/C++ the return of null would be interpreted
    // as false, but not so in Java. Hence the infinite loop that I have written
    // with a break statement for when readLine returns null
    while(true) {
      inputLine = inputReader.readLine();
      if (inputLine == null) break;
      // The following try is a so-called "try block with resources". It automatically
      // closes the resource when the try block exits. I have used continue
      // statements in this loop and the try block guarantees that the
      // lineTokenizer scanner will be closed even when a continue statement
      // is executed.
      try (Scanner lineTokenizer = new Scanner(inputLine)) {
	lineNum++;

	// determine if the line has a name field
	if (lineTokenizer.hasNext()) {
	  lineTokenizer.next();  // consume the valid token
	}
	else {
	  inputReader.printf("Line %d: line must have the format 'name age singleness'\n", lineNum);
	  continue;  // proceed to the next line of input
	}
	// determine if the line has a second field, and if so, whether that
	// field is an integer
	if (lineTokenizer.hasNext()) {
	  if (lineTokenizer.hasNextInt()) {
	    lineTokenizer.nextInt(); // consume the valid token
	  }
	  else
	    inputReader.printf("line %d - %s: age should be an integer\n", 
		lineNum, lineTokenizer.next());
	}
	else {
	  inputReader.printf("line %d: must have fields for age and singleness\n",
	      lineNum);
	  continue; // proceed to the next line of input
	}
	// determine if the line has a third field, and if so, whether that
	// field is a boolean
	if (lineTokenizer.hasNext()) {
	  if (lineTokenizer.hasNextBoolean())
	    lineTokenizer.nextBoolean(); // consume the valid token
	  else {
	    inputReader.printf("line %d - %s: singleness should be a boolean\n", 
		lineNum, lineTokenizer.next());
	    continue; // proceed to the next line of input
	  }
	}
	else {
	  inputReader.printf("line %d: must have a field for singleness\n", lineNum);
	  continue; // proceed to the next line of input
	}
	lineTokenizer.close(); // discard this line
      }
    }
  }
}