‹‹ Previous: first real entry
Next: What will Google think of next? ››
DEBUGGING: Using Java's Scanner with Different Delimiter Types
Posted by Fred on Thursday, October 9th, 2008
Yesterday a friend of mine was having some very strange errors with a program he was writing in Java for his data structures and algorithms class. He was writing a text-based address book manager as a demonstration for using trees to act like a database. The code that was causing issues was the part of the code that allowed you to change a contact's details. After carefully verifying the code, we knew that Contact was a valid class and its methods worked properly, and ContactList was also working properly. Here is the troublesome section:
Contact c = ContactList.findByName(previousInput);
//gets the contact we're worried about.
//This code was verified to work.
boolean choiceMade = true;
while(choiceMade){
choiceMade = false; //Unless the user gives us invalid
//input we'll assume it's valid and break the loop
Scanner in = new Scanner(System.in);
System.out.println("Please select which data you wish to modify (1-7)");
//List of options is printed out here
//...
int selection = in.nextInt();
switch(selection){
case "1":
//PROBLEM AREA:
System.out.println("Please enter a new first name:");
String modification = in.nextLine();
c.setFirstName(modification);
break;
//additional cases for numbers 2-7. These cases are almost identical to 1
default: //catch bad input
}
}
When this program was executed, we simply attempted to add a contact to the address book, and then modify that contact using the method containing the above code. At runtime, we received the following output: (names in xs to protect the innocent)
Please enter the contact which you wish to modify: xxxx xxxxx
Details for xxxx xxxxx:
Phone: 1234567890
Fax: 1112223333
Address: sdfdfgds
Please select which data you wish to modify (1-7):
(List of options omitted for clarity)
1
Please enter a new first name:Welcome to addressbook! Please select an option from below:
1 - Create new contact
(etc...)
This is obviously not the output we were expecting. We asked to modify the name, and it didn't even give us a chance to! What's stranger, it was printing the startup menu, which was done as part of a larger parent loop to the function listed. Stepping through the debugger provided only a minor hint, as we saw that at the line where modification is set it was always "", both before and after it was set.
The problem here is not immediately obvious, and if you are not careful can easily find its way into your code. The function's logic is sound, and the programs structure is strong. These are not the issues at hand. What is causing the problem is actually a very subtle misuse of the Scanner class. Let's take a look at a simplified example to understand more clearly what the problem is.
//assume we have a simple Test class with the following
//statements in the class' main() function:
Scanner in = new Scanner(System.in);
System.out.println(in.nextInt());
System.out.println(in.nextLine());
We would expect this code to allow us to give it a number, then any line of input, and each would be spat back out by the program. This is not, in fact, what occurs. Consider the following output that this code will produce:
5
5
(Program Terminates)
It appears we are only permitted to enter a number, and nothing after that number. This should probably provide some general indication of where the problem stems from, but let's examine what is happening here closer. Say we give our smaller example the following input:
5 [Enter]
Some other text [Enter]
Each press of the enter key signals to the operating system that you are finished providing input, and to flush the input stream to whatever has attached itself to System.in . In effect, this produces the following results. You may see this clarification as trivial, but it's actually very important.
5n
Some other textn
Stepping through our smaller example, our first call of in.nextInt() will work as expected, and spit out 5. Scanner.nextInt() looks for the next integer in its input stream, and moves the cursor it uses internally to mark its current position to the position of the next integer + 1. So let's see where our Scanner has moved to in our input stream (| denotes the cursor used by Scanner):
5|n
Some other textn
The problem should now be obvious. When we call Scanner.nextLine(), Scanner looks for the next end-line delimiter (which is different for different systems; for our purposes I will use n as above), and returns everything in the input stream between the Scanner's cursor and the next end-line delimiter, and moves the cursor to the end-line delimiter + 1. As one can see in the above input trace, there is nothing between the integer 5 and the new line. Therefore, a call to nextLine() will return "" and move the cursor as follows:
5n
|Some other textn
Therefore, nextLine returns "". Notice, we still have input ready to be parsed, but no further calls are made in Scanner and the program terminates, meaning that "Some other text" is simply ignored. It can be easy to forget about the end-line delimiter and take it for granted that it will be parsed intelligently when nextInt() is called, but it is not.
Ok, so how can we fix this problem? Simple. Call nextLine() again! Our first nextLine() call will just be used to step over the newline at the end of the numeric input, and we can then use nextLine() again to get the input we are concerned with. Here is the corrected version of my friend's code:
Contact c = ContactList.findByName(previousInput);
//gets the contact we're worried about.
//This code was verified to work.
boolean choiceMade = true;
while(choiceMade){
choiceMade = false; //Unless the user gives us invalid
//input we'll assume it's valid and break the loop
Scanner in = new Scanner(System.in);
System.out.println("Please select which data you wish to modify (1-7)");
//List of options is printed out here
//...
int selection = in.nextInt();
switch(selection){
case "1":
//EDITED to provide working solution:
System.out.println("Please enter a new first name:");
in.nextLine();
String modification = in.nextLine();
c.setFirstName(modification);
break;
//additional cases for numbers 2-7. These cases are almost identical to 1
default: //catch bad input
}
}
This problem was somewhat elusive to solve and quite irritating! I posted this in an attempt to make sure you don't run into the same problem, and if you do you can remedy it quickly and efficiently.
Happy Hacking!
Mood: 
Currently listening to: Pearl Jam - Down
Total Comments (0)
Tagged under programming, java, debugging, computers