Why does the Scanner return an error in something that is expected?

15

Note that I typed a number, a text, and a number, as you request.

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();
    }
}

See the error in 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 did not like that solution . I would like to know why it makes a mistake, what to do to solve it, and if there is no way if Victor Stafusa's solution is the best available.

    
asked by anonymous 14.12.2017 / 16:01

3 answers

15

Let's assume your entry is as follows:

5
Maniero
12

Note that nextInt() will consume 5 and return, without consuming the following line break. When nextLine() is executed, it will see the line break, consume it and give you a blank string.

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

There is 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() .

In addition to from my answer you linked , I also addressed this issue this other answer of mine , in this other one and also here .

And why does not it consume the line-break? Because you might want to do this, it will work:

Entry:

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 Scanner should consume no more input than is required to do what you were asked to do. This means that nextInt() will not consume the next line break (or anything else that follows) because that would consume more than needed at the input.

    
14.12.2017 / 16:14
5

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

  • next() fetches and returns the next TOKEN complete (returns: 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() Parses the next TOKEN of an input in returns 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 entry that was "skipped" (returns: String )

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

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 pressing Enter you type:

1    2       3

And then just hit Enter , note that it will already display the 3 System.out.println , because both space and line break will be considered to separate values, and these values between 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 enter this all before pressing Enter :

1    2      ab

After pressing Enter, all 3% with% will again be displayed, so token all work by spaces as much as line breaks.

Now the System.out.println

My translation is 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 even did not understand the ignored one, it's not that the line was "ignored", the sense of skipped there is that it passed to the next line when you ran the method, ie it would be more for something like "return the entry that was from the line that was skipped" (I do not know if skipped sounds good, maybe I'll check the text).

So, in fact, the only thing that works with the entire line instead of TOKENs is nextLine , so it's as if the entire line was a token, to explain why I'd rather use your own code (read the comments).

Search for TOKEN (you do not need to match nextLine , it can be anything other than a space), but stay on "line 1" :

int valor1 = entrada.nextInt();

We are still in line 1 , but every int function will always work after the last TOKEN, so the next* ignores, only the 1 or \n of the typed input, so in this case it will return an empty%%, since line breaks are not values for TOKENs and will go to line 2 :

String texto = entrada.nextLine();

Now we are in line 2 and not in "line 3" , but in your line 2 you typed \r\n , which does not "house" with the next command:

int valor2 = entrada.nextInt();

Then there is the exception String , following the complete input

  

123
  abc
  456

    
14.12.2017 / 19:53
1
  

I wanted to know why it gives error

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

valor1 = 123 
texto = ""

So when you make the second call to entrada.nextInt() , since there is no integer on the line, an exception occurs.

  

What to do to resolve

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

Handle user input

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

Please note that there are a number of other problems related to this reason in your code. At first call to entrada.nextInt() the program may give an error if the user enters characters other than digits.

Deal with the problem immediately

If you are looking for an immediate solution, whenever you make a call to nextXXX that is not nextLine() you should also call nextLine() accordingly. That is, reading a number should be done as follows.

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

Address the issue with due care

Eventually it may be interesting to make a method with this code and that eventually also treats the input of the user. Something like 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 can have a lot of flaws, but for sure, it at least demonstrates a greater tolerance for invalid input by the user.

    
01.07.2018 / 01:57