El escáner se salta nextLine () después de usar next () o nextFoo ()?

684

Estoy usando los Scannermétodos nextInt()y nextLine()para leer la entrada.

Se parece a esto:

System.out.println("Enter numerical value");    
int option;
option = input.nextInt(); // Read numerical value from input
System.out.println("Enter 1st string"); 
String string1 = input.nextLine(); // Read 1st string (this is skipped)
System.out.println("Enter 2nd string");
String string2 = input.nextLine(); // Read 2nd string (this appears right after reading numerical value)

El problema es que después de ingresar el valor numérico, input.nextLine()se omite el primero y input.nextLine()se ejecuta el segundo , de modo que mi salida se ve así:

Enter numerical value
3   // This is my input
Enter 1st string    // The program is supposed to stop here and wait for my input, but is skipped
Enter 2nd string    // ...and this line is executed and waits for my input

Probé mi aplicación y parece que el problema radica en el uso input.nextInt(). Si lo elimino, ambos string1 = input.nextLine()y string2 = input.nextLine()se ejecutan como quiero que sean.

blekione
fuente
55
O podrías ser como yo y usar BufferedReader :) No me importa si es de la vieja escuela, siempre ha funcionado y siempre funcionará para mí. Además, el conocimiento de BufferedReader tiene aplicación en otros lugares. Simplemente no me gusta el escáner.
Cruncher

Respuestas:

831

Esto se debe a que el Scanner.nextIntmétodo no lee el carácter de nueva línea en su entrada creada al presionar "Enter", por lo que la llamada a Scanner.nextLineregresa después de leer esa nueva línea .

Encontrará un comportamiento similar cuando use Scanner.nextLineafter Scanner.next()o cualquier Scanner.nextFoométodo (excepto él nextLinemismo).

Solución alterna:

  • Haga una Scanner.nextLinellamada después de cada uno Scanner.nextInto Scanner.nextFooconsuma el resto de esa línea, incluida la nueva línea

    int option = input.nextInt();
    input.nextLine();  // Consume newline left-over
    String str1 = input.nextLine();
  • O, mejor aún, lea la entrada Scanner.nextLiney conviértala al formato adecuado que necesita. Por ejemplo, puede convertir a un entero utilizando el Integer.parseInt(String)método.

    int option = 0;
    try {
        option = Integer.parseInt(input.nextLine());
    } catch (NumberFormatException e) {
        e.printStackTrace();
    }
    String str1 = input.nextLine();
Rohit Jain
fuente
44
@blekione. Tienes que usar try-catch, porque se Integer.parseIntlanza NumberFormatExceptioncuando se le pasa un argumento no válido. Aprenderá sobre la excepción más adelante. Por ejemplo: - Integer.parseInt("abc"). No quieres que "abc" se convierta a int ¿verdad?
Rohit Jain
3
@blekione. Entonces, en el caso anterior, su código se detendrá en ese punto y no podrá continuar la ejecución. Con el manejo de excepciones, puede manejar este tipo de condiciones.
Rohit Jain
44
¿En qué medida es mejor este último? AFAIK Scanner # nextInt () es mucho más indulgente para encontrar entradas correctas, al permitir comas grupales y prefijos y sufijos locales. Integer # parseInt () solo permite dígitos y punto decimal más un signo opcional.
Mordechai
55
Personalmente prefiero un Scanner#hasNextFoocheque de antemano en lugar de un try-catch, pero eso también funciona.
MildlyMilquetoast
1
Usar ParseDouble tiene un nuevo problema, que nextDouble usa la configuración regional de decimal (. O,) pero Parsedouble siempre recibe decimal estadounidense (.).
olmerg
209

El problema está en el método input.nextInt () : solo lee el valor int. Entonces, cuando continúe leyendo con input.nextLine (), recibirá la tecla "\ n" Enter. Entonces, para omitir esto, debe agregar input.nextLine () . Espero que esto esté claro ahora.

Pruébalo así:

System.out.print("Insert a number: ");
int number = input.nextInt();
input.nextLine(); // This line you have to add (It consumes the \n character)
System.out.print("Text1: ");
String text1 = input.nextLine();
System.out.print("Text2: ");
String text2 = input.nextLine();
Prine
fuente
umm parece una solución, por lo que la línea omitido no usada nextLine, pero todavía necesito una explicación de este comportamiento
Eng.Fouad
2
FYI: se fusionó de stackoverflow.com/questions/7056749/…
Shog9
Pregunta: realizada el 27 de octubre de 12 a las 16:37. respondió el 14 de agosto de 11 a las 12:24. ¿Cómo es esto posible?
Apoorv Patne
79

Es porque cuando ingresa un número y luego presiona Enter, input.nextInt()consume solo el número, no el "final de línea". Cuando se input.nextLine()ejecuta, consume el "final de línea" aún en el búfer desde la primera entrada.

En cambio, use input.nextLine()inmediatamente despuésinput.nextInt()

Bohemio
fuente
77
@Victor no es un error. Funciona como se especifica. Sin embargo, podría argumentar que debería haber una manera fácil de decirle a la API que también consuma cualquier espacio en blanco después de la entrada de destino.
Bohemio
Veo. gracias @Bohemian, eso es exactamente lo que estoy discutiendo. Creo que esto debería cambiarse, tal vez pueda indicarme dónde sugerir este "problema" en el próximo PCJ.
Victor
@victor Puede solicitar (y descubrir cómo contribuir con código) una función a través de la página Informar un error o Solicitar una función .
Bohemio
FYI: se fusionó de stackoverflow.com/questions/7056749/…
Shog9
48

Parece que hay muchas preguntas sobre este problema con java.util.Scanner. Creo que una solución más legible / idiomática sería llamar scanner.skip("[\r\n]+")para eliminar los caracteres de nueva línea después de llamar nextInt().

EDITAR: como @PatrickParker señala a continuación, esto causará un bucle infinito si el usuario ingresa cualquier espacio en blanco después del número. Vea su respuesta para un mejor patrón para usar con skip: https://stackoverflow.com/a/42471816/143585

Denis Tulskiy
fuente
FYI: se fusionó de stackoverflow.com/questions/7056749/…
Shog9
Sé lo que hacemos para eliminar los datos en el búfer, pero en este caso, ayúdenme
Khuong
1
Para su información, esto causará un bucle infinito si el usuario escribe cualquier pestaña o espacio después de la entrada numérica. Vea mi respuesta aquí para un mejor salto: stackoverflow.com/a/42471816/7098259
Patrick Parker
@PatrickParker: de hecho, ¡causa un bucle infinito! Gracias, editó la respuesta para vincular a la suya.
Denis Tulskiy
33

Lo hace porque input.nextInt();no captura la nueva línea. puedes hacer como los otros propuestos agregando un input.nextLine();debajo.
Alternativamente, puede hacerlo al estilo C # y analizar una nextLine a un entero de esta manera:

int number = Integer.parseInt(input.nextLine()); 

Hacer esto funciona igual de bien y le ahorra una línea de código.

Cafe electrico
fuente
FYI: se fusionó de stackoverflow.com/questions/7056749/…
Shog9
1
tienes que usar try catch aquí. Qué sucederá si la entrada no es un número. NumberFormatException necesita ser manejado aquí.
Arundev
Esto supone que solo hay un token int en cada línea para la que prueba esto.
cellepo
25

TL; DR Uso scanner.skip("\\R")antes de cada scanner.newLine()llamada, que se ejecuta después de:

  • scanner.next()
  • scanner.next*TYPE*() método.

Cosas que necesitas saber:

  • el texto que representa pocas líneas también contiene caracteres no imprimibles entre líneas (los llamamos separadores de línea) como

    • retorno de carro (CR - en literales de cadena representados como "\r")
    • avance de línea (LF - en literales de cadena representados como "\n")
  • cuando está leyendo datos desde la consola, le permite al usuario escribir su respuesta y cuando termina necesita confirmar de alguna manera ese hecho. Para hacerlo, el usuario debe presionar la tecla "enter" / "return" en el teclado.

    Lo importante es que esta clave, además de garantizar la colocación de los datos del usuario en la entrada estándar (representada por la System.incual se lee Scanner), también envía separadores de línea dependientes del sistema operativo (como para Windows \r\n) después de ella.

    Entonces, cuando le pide al usuario un valor como age, y el usuario escribe 42 y presiona enter, la entrada estándar contendrá "42\r\n".

Problema

Scanner#nextInt(y otros métodos) no permite que ScannerScanner#nextType consuma estos separadores de línea. Los leerá System.in(¿de qué otra manera Scanner sabría que no hay más dígitos del usuario que representen un agevalor que no sean espacios en blanco?) Que los eliminará de la entrada estándar, pero también almacenará en caché esos separadores de línea internamente . Lo que debemos recordar es que todos los métodos de escáner siempre escanean a partir del texto almacenado en caché.

Ahora Scanner#nextLine() simplemente recopila y devuelve todos los caracteres hasta que encuentre separadores de línea (o final de la secuencia). Pero dado que los separadores de línea después de leer el número de la consola se encuentran inmediatamente en la memoria caché de Scanner, devuelve una cadena vacía, lo que significa que Scanner no pudo encontrar ningún carácter antes de esos separadores de línea (o al final de la secuencia).
Por cierto, nextLinetambién consume esos separadores de línea.

Solución

Así que cuando se quiere pedir el número y luego por toda la línea, evitando que cadena vacía como resultado de nextLine, o bien

  • consumir separador de línea dejado por la nextIntmemoria caché de escáneres
    • llamando nextLine,
    • O la forma más legible de la OMI sería llamar skip("\\R")o skip("\r\n|\r|\n")dejar que el escáner omita la parte que coincida con el separador de línea (más información sobre \R: https://stackoverflow.com/a/31060125 )
  • no use nextInt(ni next, ni ningún nextTYPEmétodo) en absoluto. En su lugar, lea los datos completos línea por línea usando nextLiney analice los números de cada línea (suponiendo que una línea contenga solo un número) al tipo apropiado como intvia Integer.parseInt.

Por cierto : los métodos pueden omitir delimitadores (de forma predeterminada, todos los espacios en blanco, como tabulaciones, separadores de línea), incluidos los almacenados en caché por el escáner, hasta que encuentren el siguiente valor que no sea delimitador (token). Gracias a eso por entrada como códigoScanner#nextType"42\r\n\r\n321\r\n\r\n\r\nfoobar"

int num1 = sc.nextInt();
int num2 = sc.nextInt();
String name = sc.next();

podrá asignar adecuadamente num1=42 num2=321 name=foobar.

Pshemo
fuente
14

En lugar de input.nextLine()usar input.next(), eso debería resolver el problema.

Código modificado:

public static Scanner input = new Scanner(System.in);

public static void main(String[] args)
{
    System.out.print("Insert a number: ");
    int number = input.nextInt();
    System.out.print("Text1: ");
    String text1 = input.next();
    System.out.print("Text2: ");
    String text2 = input.next();
}
Castaldi
fuente
FYI: se fusionó de stackoverflow.com/questions/7056749/…
Shog9
13

Si desea leer cadenas e ints, una solución es usar dos escáneres:

Scanner stringScanner = new Scanner(System.in);
Scanner intScanner = new Scanner(System.in);

intScanner.nextInt();
String s = stringScanner.nextLine(); // unaffected by previous nextInt()
System.out.println(s);

intScanner.close();
stringScanner.close();
André Valenti
fuente
1
Esto es lo que estaba buscando, desde hace algún tiempo. ¡Movimiento inteligente!
Liam Wilson el
1
@BdLearner scanner.nextLine()debería funcionar.
André Valenti
11

Si desea escanear entradas rápidamente sin confundirse con el método nextLine () de la clase Scanner, use el escáner de entrada personalizado para ello.

Código:

class ScanReader {
/**
* @author Nikunj Khokhar
*/
    private byte[] buf = new byte[4 * 1024];
    private int index;
    private BufferedInputStream in;
    private int total;

    public ScanReader(InputStream inputStream) {
        in = new BufferedInputStream(inputStream);
    }

    private int scan() throws IOException {
        if (index >= total) {
            index = 0;
            total = in.read(buf);
            if (total <= 0) return -1;
        }
        return buf[index++];
    }
    public char scanChar(){
        int c=scan();
        while (isWhiteSpace(c))c=scan();
        return (char)c;
    }


    public int scanInt() throws IOException {
        int integer = 0;
        int n = scan();
        while (isWhiteSpace(n)) n = scan();
        int neg = 1;
        if (n == '-') {
            neg = -1;
            n = scan();
        }
        while (!isWhiteSpace(n)) {
            if (n >= '0' && n <= '9') {
                integer *= 10;
                integer += n - '0';
                n = scan();
            }
        }
        return neg * integer;
    }

    public String scanString() throws IOException {
        int c = scan();
        while (isWhiteSpace(c)) c = scan();
        StringBuilder res = new StringBuilder();
        do {
            res.appendCodePoint(c);
            c = scan();
        } while (!isWhiteSpace(c));
        return res.toString();
    }

    private boolean isWhiteSpace(int n) {
        if (n == ' ' || n == '\n' || n == '\r' || n == '\t' || n == -1) return true;
        else return false;
    }

    public long scanLong() throws IOException {
        long integer = 0;
        int n = scan();
        while (isWhiteSpace(n)) n = scan();
        int neg = 1;
        if (n == '-') {
            neg = -1;
            n = scan();
        }
        while (!isWhiteSpace(n)) {
            if (n >= '0' && n <= '9') {
                integer *= 10;
                integer += n - '0';
                n = scan();
            }
        }
        return neg * integer;
    }

    public void scanLong(long[] A) throws IOException {
        for (int i = 0; i < A.length; i++) A[i] = scanLong();
    }

    public void scanInt(int[] A) throws IOException {
        for (int i = 0; i < A.length; i++) A[i] = scanInt();
    }

    public double scanDouble() throws IOException {
        int c = scan();
        while (isWhiteSpace(c)) c = scan();
        int sgn = 1;
        if (c == '-') {
            sgn = -1;
            c = scan();
        }
        double res = 0;
        while (!isWhiteSpace(c) && c != '.') {
            if (c == 'e' || c == 'E') {
                return res * Math.pow(10, scanInt());
            }
            res *= 10;
            res += c - '0';
            c = scan();
        }
        if (c == '.') {
            c = scan();
            double m = 1;
            while (!isWhiteSpace(c)) {
                if (c == 'e' || c == 'E') {
                    return res * Math.pow(10, scanInt());
                }
                m /= 10;
                res += (c - '0') * m;
                c = scan();
            }
        }
        return res * sgn;
    }

}

Ventajas:

  • Escanea la entrada más rápido que BufferReader
  • Reduce la complejidad del tiempo
  • Descarga el búfer para cada entrada siguiente

Métodos :

  • scanChar (): escanea un solo carácter
  • scanInt () - escanea el valor entero
  • scanLong () - valor de escaneo largo
  • scanString () - valor de cadena de escaneo
  • scanDouble () - escanea el valor doble
  • scanInt (matriz [] [int]): analiza la matriz completa (entero)
  • scanLong (matriz larga []): analiza la matriz completa (larga)

Uso:

  1. Copie el Código dado debajo de su código Java.
  2. Inicializar objeto para clase dada

ScanReader sc = new ScanReader(System.in); 3. Importar clases necesarias:

import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; 4. Lanza IOException desde tu método principal para manejar la excepción 5. Usa los métodos provistos. 6. Disfruta

Ejemplo:

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
class Main{
    public static void main(String... as) throws IOException{
        ScanReader sc = new ScanReader(System.in);
        int a=sc.scanInt();
        System.out.println(a);
    }
}
class ScanReader....
NIKUNJ KHOKHAR
fuente
10

Para evitar el problema, use nextLine();inmediatamente después, nextInt();ya que ayuda a limpiar el búfer. Cuando presiona ENTERel nextInt();botón no captura la nueva línea y, por lo tanto, omite el Scannercódigo más tarde.

Scanner scanner =  new Scanner(System.in);
int option = scanner.nextInt();
scanner.nextLine(); //clearing the buffer
Urvashi Gupta
fuente
9

sc.nextLine()es mejor en comparación con el análisis de la entrada. Porque el rendimiento sabio será bueno.

shankar upadhyay
fuente
5

Supongo que llego bastante tarde a la fiesta ...

Como se indicó anteriormente, llamar input.nextLine()después de obtener su valor int resolverá su problema. La razón por la cual su código no funcionó fue porque no había nada más que almacenar desde su entrada (donde ingresó el int) enstring1 . Voy a arrojar un poco más de luz sobre todo el tema.

Considere nextLine () como el extraño entre los métodos nextFoo () en la clase Scanner. Tomemos un ejemplo rápido. Digamos que tenemos dos líneas de código como las siguientes:

int firstNumber = input.nextInt();
int secondNumber = input.nextInt();

Si ingresamos el valor a continuación (como una sola línea de entrada)

54 234

El valor de nuestra firstNumbery secondNumbervariable se convierte en 54 y 234 respectivamente. La razón por la que esto funciona de esta manera es porque un nuevo avance de línea ( es decir, \ n ) NO SE genera automáticamente cuando el método nextInt () toma los valores. Simplemente toma el "próximo int" y sigue adelante. Esto es lo mismo para el resto de los métodos nextFoo () excepto nextLine ().

nextLine () genera un nuevo avance de línea inmediatamente después de tomar un valor; esto es lo que @RohitJain quiere decir al decir que el nuevo avance de línea se "consume".

Por último, el método next () simplemente toma la cadena más cercana sin generar una nueva línea; Esto lo convierte en el método preferencial para tomar cadenas separadas dentro de la misma línea.

Espero que esto ayude. ¡Feliz codificación!

Taslim Oseni
fuente
¿Qué es un "nuevo avance de línea"?
Archer el
@Abcd Un nuevo avance de línea básicamente significa 'comenzar desde una nueva línea'.
Taslim Oseni
1
Ese ejemplo "54 234" realmente aclara las cosas.
Alonso del Arte
3

Use 2 objetos de escáner en lugar de uno

Scanner input = new Scanner(System.in);
System.out.println("Enter numerical value");    
int option;
Scanner input2 = new Scanner(System.in);
option = input2.nextInt();
Harh Shah
fuente
2
public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int i = scan.nextInt();
        scan.nextLine();
        double d = scan.nextDouble();
        scan.nextLine();
        String s = scan.nextLine();

        System.out.println("String: " + s);
        System.out.println("Double: " + d);
        System.out.println("Int: " + i);
    }
Neeraj Gahlawat
fuente
Si usa el método nextLine () inmediatamente después del método nextInt (), recuerde que nextInt () lee tokens enteros; debido a esto, el último carácter de nueva línea para esa línea de entrada entera todavía está en cola en el búfer de entrada y la siguiente nextLine () leerá el resto de la línea entera (que está vacía).
Neeraj Gahlawat
2

si espero una entrada no vacía

public static Function<Scanner,String> scanLine = (scan -> {
    String s = scan.nextLine();
    return( s.length() == 0 ? scan.nextLine() : s );
  });


utilizado en el ejemplo anterior:

System.out.println("Enter numerical value");    
int option = input.nextInt(); // read numerical value from input

System.out.println("Enter 1st string"); 
String string1 = scanLine.apply( input ); // read 1st string
System.out.println("Enter 2nd string");
String string2 = scanLine.apply( input ); // read 2nd string
Kaplan
fuente
2

En uno de mis casos de uso, tuve el escenario de leer un valor de cadena precedido por un par de valores enteros . Tuve que usar un " for / while loop " para leer los valores. Y ninguna de las sugerencias anteriores funcionó en este caso.

Usando en input.next()lugar de input.nextLine()solucionado el problema. Espero que esto pueda ser útil para aquellos que se enfrentan a situaciones similares.

Giri
fuente
2

Use este código, solucionará su problema.

System.out.println("Enter numerical value");    
int option;
option = input.nextInt(); // Read numerical value from input
input.nextLine();
System.out.println("Enter 1st string"); 
String string1 = input.nextLine(); // Read 1st string (this is skipped)
System.out.println("Enter 2nd string");
String string2 = input.nextLine(); // Read 2nd string (this appears right after reading numerical value)
Jeewantha Lahiru
fuente
2

Como los nextXXX()métodos no leen newline, excepto nextLine(). Podemos omitir newlinedespués de leer cualquier non-stringvalor ( inten este caso) usando scanner.skip()lo siguiente:

Scanner sc = new Scanner(System.in);
int x = sc.nextInt();
sc.skip("(\r\n|[\n\r\u2028\u2029\u0085])?");
System.out.println(x);
double y = sc.nextDouble();
sc.skip("(\r\n|[\n\r\u2028\u2029\u0085])?");
System.out.println(y);
char z = sc.next().charAt(0);
sc.skip("(\r\n|[\n\r\u2028\u2029\u0085])?");
System.out.println(z);
String hello = sc.nextLine();
System.out.println(hello);
float tt = sc.nextFloat();
sc.skip("(\r\n|[\n\r\u2028\u2029\u0085])?");
System.out.println(tt);
Sandeep Kumar
fuente
0

¿Por qué no usar un nuevo escáner para cada lectura? Como abajo. Con este enfoque no enfrentarás tu problema.

int i = new Scanner(System.in).nextInt();
Tobias Johansson
fuente
3
Pero luego debe cerrar Scannerpara evitar pérdidas de memoria. ¿Perdiendo el tiempo?
TheCoffeeCup
1
¿El GC no se ocupa de eso si el escáner está envuelto en un método? Me gusta: String getInput () {return new Scanner (System.in) .nextLine ()};
Tobias Johansson
Esto es absolutamente incorrecto. nextInt()no consumirá la nueva línea, independientemente de si está en una "nueva" Scannero ya usada.
Dawood ibn Kareem