Why does Scanner return error on something that is within the expected?

Asked

Viewed 2,012 times

16

Note that I have typed a number, a text and a number, as you ask there.

import java.util.Scanner;

class Ideone {
    public static void main (String[] args) {
        Scanner entrada = new Scanner(System.in);
        int valor1 = entrada.nextInt();
        String texto = entrada.nextLine();
        int valor2 = entrada.nextInt();
    }
}

Behold the error in the ideone.

Exception in thread "main" java.util.NoSuchElementException
  at java.util.Scanner.throwFor(Scanner.java:862)
  at java.util.Scanner.next(Scanner.java:1485)
  at java.util.Scanner.nextInt(Scanner.java:2117)
  at java.util.Scanner.nextInt(Scanner.java:2076)
  at Ideone.main(Main.java:6)

I didn’t like it of that solution. I’d like to know why it’s wrong, what to do about it, and if it’s no good if Victor Stafusa’s solution is the best available.

  • 2

    Something tells me it’s duplicate, but I couldn’t find it.

  • I agree. I’ve answered that three times.

  • But I will not close as duplicate because in this one, this is the core of the problem, and not something tangential, allowing then a canonical answer.

  • I was going to close ;)

  • 1

    Just to explain otherwise, next, nextInt, nextLong, and others look for a specific "TOKEN" (regardless of the number of characters), nextLine looks for a line (in the case the entire line until the break is the token for nextLine), so the "problem occurs".

3 answers

17


Let’s assume your entry is as follows:

5
Maniero
12

Note that the nextInt() will consume the 5 and return, without consuming the break-of-line that follows. When the nextLine() run, it will see the line-breaking, consume it and give you a blank string.

The problem is that the nextInt() does not consume the line break that follows. To make it consume, the solution is to read the entire line (nextLine()) and use the Integer.parseInt(String) to remove the number from there.

There’s an important detail in interpreting this: What you really want is a line containing a number, a text and another line containing a number. And if you’re reading lines, use nextLine().

Afterlife of my answer that you linked, I have also addressed this problem in this other answer of mine, in this other also and also in this one.

And why doesn’t it consume line-breaking? Because you might want to do this, it will work:

Entree:

1 2 3

Program:

import java.util.Scanner;

class Ideone {
    public static void main (String[] args) {
        Scanner entrada = new Scanner(System.in);
        int valor1 = entrada.nextInt();
        int valor2 = entrada.nextInt();
        int valor3 = entrada.nextInt();
    }
}

The idea is that the Scanner should not consume more input than is necessary to do what you have been asked to do. This means that the nextInt() will not consume the line-break that follows (or anything else that follows) because that would be consuming more than necessary on input.

  • Corroborating this: https://ideone.com/Md7fKv ; after each reading I had it printed, the rescued text was "", not "abc"

  • Very good, I already had this problem and corrected as suggested in my project: https://github.com/giuliana-bezerra/project_algorithmos. I use a menu that reads integers and lines, and so I had to run nextInt followed by nextLine.

5

The class Scanner has several methods with the term next*, as being:

  • next() search and return the next complete TOKEN (return: String)

  • nextBigDecimal() Scans the next TOKEN of an input and returns as BigDecimal

  • nextBigInteger() Scans the next TOKEN of an input and returns as BigInteger

  • nextBoolean() Analyzes the next TOKEN of an input in returns in a value boolean

  • nextByte() Scans the next TOKEN of an input and returns as byte

  • nextDouble() Scans the next TOKEN of an input and returns as double

  • nextFloat() Scans the next TOKEN of an input and returns as float

  • nextInt() Scans the next TOKEN of an input and returns as int

  • nextLong() Scans the next TOKEN of an input and returns as long

  • nextShort() Scans the next TOKEN of an input and returns as short

  • nextLine() Advance this scanner beyond the current line and return the input that was "skipped" (returns: String)

Forgetting the nextLine(), note that they all speak of such a TOKEN, "but what is the TOKEN?", TOKEN there refers to something that is expected, in the example nextInt expects something that was typed to "marry" the int, then if you do it:

Scanner entrada = new Scanner(System.in);
int valor1 = entrada.nextInt();
int valor2 = entrada.nextInt();
int valor3 = entrada.nextInt();

System.out.println("Retornou:" + valor1);
System.out.println("Retornou:" + valor2);
System.out.println("Retornou:" + valor3);

But instead of squeezing Enter you type:

1    2       3

And then only then to tighten the Enter, note that it will already display the 3 System.out.println, this because both space and line break will be considered to separate the values, and these values between the separations are the TOKENS, whatever the value.

Now change to this:

Scanner entrada = new Scanner(System.in);
String valor1 = entrada.next();
String valor2 = entrada.next();
String valor3 = entrada.next();

System.out.println("Retornou:" + valor1);
System.out.println("Retornou:" + valor2);
System.out.println("Retornou:" + valor3);

And type all this before you press the Enter:

1    2      ab

After pressing Enter, again all 3 will be displayed System.out.println, then token in all work for spaces as much as break lines.

Now the nextLine

My translation got a little bad, the original text of the documentation is this:

Advances this scanner Past the Current line and Returns the input that was skipped.

I think this text is what confuses a lot of people, when translating skipped the first time I myself did not understand the ignored, is not that the line was "ignored", the meaning of skipped there is that went to the next line when executed the method, ie it would be more for something like "returns the entry that was the line that was skipped" (I do not know if skipped sounds good, maybe I review the text).

So actually the only one who works with the entire line instead of the Tokens is the nextLine, ie it is as if the entire line is a token, to explain better I will use your own code (read the comments).

Find the TOKEN (you don’t have to marry int, may be anything other than a space), but remains in the "line 1":

int valor1 = entrada.nextInt();

We’re still in the "line 1", but every function next* will always work after the last TOKEN, then the 1 you ignore, only leaves the \n or \r\n input, then in case it will return a String empty, since line breaks are not values for Tokens and will pass to the "line 2":

String texto = entrada.nextLine();

Now we’re in the "line 2" and not in the "row 3", but in his "line 2" you had typed abc, what not "home" with the next command:

int valor2 = entrada.nextInt();

Then occurs the a Exception InputMismatchException, following the complete input

123
abc
456

2

I wonder why it’s wrong

The program gives error because the first call to entrada.nextInt() does not change line. so when Voce calls entrada.nextLine() the scanner will change line and return an empty string. That is to this point have to:

valor1 = 123 
texto = ""

So that when Voce makes the second call to entrada.nextInt(), as there is no other integer on the line, an exception occurs.

What to do to solve

The resolution depends on the operation of your program, as well as, establish strictly how the user should enter the data in your application. In this case I will assume that Voce wants to keep its input as it is.

Handling the user input

In general, you should make your code so that if the user enters the data in an unexpected format, he or she may have information about the problem and can rectify it..

Please note that in your code there may be several other problems related to this reason. Right at the first call to entrada.nextInt() the program may give an error if the user enters characters other than digits.

Treat the problem immediately

If you are looking for the immediate solution, whenever you make a call to nextXXX other than nextLine() Voce should also call nextLine() consequently. That is, reading a paragraph should be done as follows.

 int valor1 = entrada.nextInt();  
 entrada.nextLine();

Treat the problem with due care

Eventually it may be interesting to make a method with this code that eventually also handles the user input. Something similar to this

private static Scanner entrada = new Scanner(System.in);
public static int readInt(String name){
    System.out.println("Introduza " + name);
    while(true){
        try{
            int valor = entrada.nextInt();
            return valor;
        }catch(Exception e){
             System.out.print(name + " deve ser um inteiro. Tente novamente.");
        }finally{
            entrada.nextLine();
        }
    }    
}   

This code may have a lot of defects, but it certainly shows at least a higher tolerance to invalid input by the user.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.