Subscribe to ...considered harmful        RSS Feed
-----

Java: "Why isn't my input inputting?"

Icon Leave Comment
One of the most common beginner traps in Java is the behavior of the "convenience" methods of the Scanner class. The usual scenario is this: the assignment is to write code accepting inputs of various types and process those inputs in some fashion. For example the assignment might be to prompt the user for a String, a substring, an index and a replacement substring, and insert the replacement into the original string in place of the substring.
So the user writes something like

import java.util.Scanner;

public class Project02 {

	public static void main(String[] args) {

		Scanner keyboard = new Scanner(System.in);
		System.out.print("Enter a long string: ");
		String lString = keyboard.nextLine();
		System.out.print("Enter a substring: ");
		String sString = keyboard.nextLine();
		System.out.println("Length of your string: " +lString.length());
		System.out.println("Length of your substring: " +sString.length());
		System.out.println("Starting position of your substring in string: " +lString.indexOf(sString) );
		System.out.println("String before your substring: " + lString.substring(0, lString.indexOf(sString)));
		System.out.println("String after your substring: " + lString.substring( (lString.indexOf(sString) + sString.length()) , lString.length())); 
		System.out.print("Enter a position between 0 and " +(lString.length()-1)+ ": ");
		int position = keyboard.nextInt();
		System.out.println("The character at position " +position+ " is " + (lString.charAt(position)));
		System.out.print("Enter a replacement string: ");
		String newString = keyboard.nextLine();
		System.out.println("Your new string is: " +(lString.replace(sString, newString)));
		
	}

}


(code courtesy of DIC member blvckphvroh)

And this looks like it should work, but it fails: the user can't enter the replacement substring, and the output is incorrect.

There is a straightforward technical fix, which is easy to apply without understanding it: simply add a call to nextLine() after the nextInt() call. This will work, but it provides no understanding, and this problem is subtle, and worth understanding in depth because it underscores the importance of understanding the library code you're working with. So we'll go into it in depth.

What is happening here is actually that the Scanner object is behaving as designed. The Scanner is a multipurpose tool meant for parsing input from various sources of character input, one of which is the standard input - but it can also read from a File or other input streams. In order to accommodate these various purposes, it provides two "next" calls: next() and nextLine(). The behavior of these is straightforward: the next() call takes a token from the current stream up to the next delimiter, which by default is any whitespace. It returns the accepted token as a String, leaving the delimiter in the stream. The nextLine() call, by contrast, accepts a line of text, meaning the input up to and including the next newline character. It consumes the newline and returns the rest of the input line. This is a simple and flexible model, and it works well for most scenarios.

The Scanner also provides some "convenience methods" for accepting a token of input as some type - typically an int or a double. This is done by calling the next() method, and attempting to parse the result as the desired type. If the parsing succeeds, the parsed input is returned as an instance of that type, and if it fails an Exception is thrown. All of this is quite reasonable - but notice that we've called next() here. And this is where we get into trouble: recall that the next() method leaves the delimiter in the stream. This means that when the user types a number, say "7" and hits return, the Scanner gets a stream consisting of "7\n". When the nextInt() call happens, the "7" is removed, and becomes a 7, and now the stream is "\n". Now we call nextLine() again, and the Scanner does the right thing: it takes the String "\n" and discards the "\n" and returns the remainder, which is "", the empty String. And the program fails to do the right thing.

The reason for the straightforward technical fix is now clear: it consumes that pesky newline, meaning that the user now is allowed to enter their desired replacement. However, this seems like a clumsy solution. Do we really want to have to think about where in our code we need to call nextLine() and throw away the result? I don't.

My suggestion to the new programmer is that you implement your own convenience methods that work the way you think they ought to: they take an input and try to make it into an instance of the desired type, and return that instance, but they also consume the rest of the input and discard it, leaving the Scanner in the correct state to accept a new input.
This is not a difficult piece of functionality to implement, and it will work better than the suggested workaround, and it'll make you happier because you'll be using code that you wrote to extend and improve Java - which is the whole point of object-oriented programming to begin with.


This leaves only the question of "why on earth did they do it that way?". This is a worthwhile question, and the answer is not obvious to the novice programmer. The reason for this design decision is to allow the nextInt method to consume, for example, all of the ints on a line, using a loop to populate a Collection such as a List. If the method required a newline, your input file would have to have one entry per line, which would be a burdensome restriction on the user of the Scanner class and would constrain the file formats they could work with. So it makes sense to have a method with the nextInt() behavior. However, it is less clear why there is not a method to "get next line as int" - but again, since we're working in an extensible language, you can repair that omission.

0 Comments On This Entry

 

Trackbacks for this entry [ Trackback URL ]

There are no Trackbacks for this entry

Recent Entries

August 2014

S M T W T F S
     12
3456789
10111213141516
171819 20 212223
24252627282930
31