Autor Tema: #Scanner Java falla nextLine al leer dato numérico diferencia con BufferedReader  (Leído 1575 veces)

CoduJ

  • Sin experiencia
  • *
  • Mensajes: 28
    • Ver Perfil
Buenas, espero que se encuentren bien.
(Detalles sobre el programa, por si necesitan esa información)
En mi último proyecto, intenté programar un registro de usuario, el trabajo de este es simple, después del registro, el usuario tiene que volver a colocar sus datos (Nombre y contraseña), para que después el programa los compare, en el caso de que los datos coincidan, se imprimirá un mensaje en la pantalla diciendo que el "registro" de la cuenta se haya creado de manera satisfactoria, acompañado con un mensaje con el nombre del usuario, de lo contrario, el programa preguntará si quieres crear una nueva cuenta, en el caso de que no, pues cierra el programa, en el caso de que sí, se activará un bolean.
(Aquí mi duda)
Bien, y para esto utilizaba obviamente una clase Scanner, para poder introducir datos por teclado, y todas las variables eran dependientes de solo un objeto Scanner (llamado "Input").
Todas estas acciones, dependían de una booleana, ya que con esta inicializaba un bucle do-while, y he aquí mi duda, al momento de que el programa iniciará todo iba normal, pero al momento de repetir el proceso mediante el do-while, por alguna razón, en la parte en donde se tenía que colocar un nombre, se saltaba, quedando un espacio en vacío, no tenía ni idea del porqué pasaba (incluso creí que era bug del IDE), pero resultaba que al momento de introducir el nombre del usuario, la variable String estaba ocupada, incluso si en algún momento del código especificaba  sobre dejarla vacía (Usuario = "") no funcionaba, pero en cuanto cambie de objeto Scanner se soluciono, ¿por qué pasa esto?, ¿qué me recomiendan para utilizar mejor la clase Scanner?
« Última modificación: 25 de Octubre 2020, 20:49 por Alex Rodríguez »

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 737
    • Ver Perfil
Re:El tema de los Scanners en Java
« Respuesta #1 en: 17 de Abril 2020, 11:38 »
Sin ver el código original no sabría decirte que ocurre.

Sí puedo comentar que un problema muy habitual es que, tras leer un dato numérico con nextInt(), nextFloat(), etc....
Si después intentamos leer un String con nextLine(), esta lectura fallará.

Por ejemplo:

Citar
System.out.print("Introduce tu numero: );
int numero = input.nextInt();

System.out.print("Introduce tu nombre: );
String nombre = input.nextLine();

En este código no se podrá introducir el nombre, se quedará como una cadena vacía.
¿Por qué?
Porque cuando introducimos el número, tecleamos el número y además pulsamos la tecla intro para terminar la entrada.
Esa tecla intro, aunque no lo veamos, genera un carácter especial llamado "retorno de carro".

Entonces, nextInt() va a coger del input aquello que le sirva para construir un valor Integer, así que cogerá los números tecleados por el usuario, pero no cogerá el carácter retorno de carro.

Ese carácter no desaparece, se queda en el flujo de datos del input.

Luego, viene la lectura con nextLine(). Este método cogerá todo lo necesario para construir una línea completa. Para ello, necesita recoger también el carácter retorno de carro, que es lo que completa y finaliza una línea.

¿Que ocurre?
Que al pedir el nombre, nextLine() se encuentra que en el flujo de datos ya hay un retorno de carro, el que nextInt() dejó abandonado. Entonces nextLine() se confunde, piensa que el usuario ya ha terminado de teclear el nombre y da por finalizada la entrada de datos.

Por eso, siempre que intentemos leer un String con nextLine(), después de haber leído un valor numérico, esa lectura fallará.
No se si será este tu caso.

Por supuesto, tiene solución.

Una es que al terminar de leer datos numéricos, hagamos un nextLine() para eliminar cualquier retorno de carro que haya quedado en el input.
No hace falta recogerlo en ninguna variable, simplemente invocamos al método:
Citar
System.out.print("Introduce tu numero: );
int numero = input.nextInt();
input.nextLine() //Eliminamos carácter retorno de carro del flujo de datos

System.out.print("Introduce tu nombre: );
String nombre = input.nextLine();


Otra solución, es hacer TODAS las lecturas usando siempre nextLine().
Entonces, si queremos un dato numérico, lo que hacemos es parsear(transformar) el String que nos ha dado nextLine(), en el tipo de dato numérico que deseamos:

Citar
System.out.print("Introduce tu numero: );
int numero = Integer.parseInt(input.nextLine()); //Leemos String y parseamos a Integer

System.out.print("Introduce tu nombre: );
String nombre = input.nextLine();

Cualquier forma es válida, pero quizás sea mejor esta última, por dos motivos:

1- Más adelante, cuando hagas interfaces gráficas con ventanas, botones, campos de texto... los datos SIEMPRE los vas a recibir como un String.
Así que siempre vas a tener que parsear al tipo de dato numérico que necesites.
Por lo tanto, no viene mal ir cogiendo ya la costumbre.

2- Esta forma de leer datos, directamente como String para automáticamente parsear a valor numérico, es la forma habitual de leer datos en otros lenguajes que seguramente algún día querrás meterles mano como C#.
Así que también habrás adquirido ya esta costumbre.


Insisto, no se si era este tu problema. Si pudiéramos ver el código quizás te pudiéramos informar mejor.

Un saludo.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

CoduJ

  • Sin experiencia
  • *
  • Mensajes: 28
    • Ver Perfil
Re:El tema de los Scanners en Java
« Respuesta #2 en: 18 de Abril 2020, 03:50 »
Mmm... Vale, creo que lo he captado.

Entonces, lo que está pasando ahí es que al tener de "denominador" las entradas numéricas estas se le suelen tener más preferencia que las de datos String, omitiendo el paso a que la entrada del valor String ya se haya considerado "ocupada". (Aún estoy tratando de entender esto... Lamento las molestias.)

Y bueno, el código:

Código: [Seleccionar]
import java.util.Scanner;

public class Practica_1{
   public static void main(String args[]){
      int OPC_CrearCuenta = 0;
      boolean IntentoLogin = false;
      String Usuario, PassW, UsuarioL, PassWL;
      Scanner Input = new Scanner(System.in);
      Scanner InputUsuario = new Scanner(System.in);
      Scanner InputPassW = new Scanner(System.in);
     
      do{
         //registro
         System.out.println("--- INICIO DE SESION ---\n\tRegistro");
         System.out.print("Nombre de ususario: ");
         Usuario = InputUsuario.nextLine();
         System.out.print("Contraseña: ");
         PassW = InputPassW.nextLine();
     
         //inicio
         System.out.println("--- INICIO DE SESION ---\n\tIniciar");
         System.out.print("Nombre de ususario: ");
         UsuarioL = InputUsuario.nextLine();
         System.out.print("Contraseña: ");
         PassWL = InputPassW.nextLine();
     
     
         //ver si los datos coinciden
         if (UsuarioL.equals(Usuario) && PassWL.equals(PassW)){
            System.out.println("Has ingresado correctamente\n\tBienvenido " + Usuario);
            IntentoLogin = false;
         }else{
            System.out.println("Has ingresado el usuario de manera incorrecta");
            System.out.println("¿Deseas crear una nueva cuenta?\n1: SÍ | 2: NO");
            OPC_CrearCuenta = Input.nextInt();
         
            while(OPC_CrearCuenta > 2 || OPC_CrearCuenta < 1){ //mientras que el ususario no de la respuesta correcta
               System.out.println("No has ingresado de la manera correcta. . .");
               System.out.println("¿Deseas crear una nueva cuenta?\n1: SÍ | 2: NO");
               OPC_CrearCuenta = Input.nextInt();
            }
            //en el caso de crear cuenta
            if (OPC_CrearCuenta == 1){
               System.out.println("Has decidido crear una nueva cuenta. . .");
               IntentoLogin = true;
            }else if (OPC_CrearCuenta == 2){
               System.out.println("Has decidido no crear la cuenta. . .");
               IntentoLogin = false;
            }
         }
      }while(IntentoLogin == true);
   }
}
« Última modificación: 25 de Octubre 2020, 20:46 por Alex Rodríguez »

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 737
    • Ver Perfil
Re:El tema de los Scanners en Java
« Respuesta #3 en: 18 de Abril 2020, 11:21 »

Mmm... Vale, creo que lo he captado.
Entonces, lo que está pasando ahí es que al tener de "denominador" las entradas numéricas estas se le suelen tener más preferencia que las de datos String, omitiendo el paso a que la entrada del valor String ya se haya considerado "ocupada".
No exactamente, no es un tema de preferencias.

Mira, si yo introduzco un número, por ejemplo el 45, tras pulsar enter en  la memoria del ordenador tenemos esto:

45\r

Ese \r es el carácter retorno de carro.

Si eso que está en memoria lo recoge un nextInt(), solo va a coger lo que le interesa, los números.
Así que en memoria tenemos esto ahora:
\r

Si ahora pedimos a nextLine() que entre en acción, porque el usuario va a teclear una línea String, se va a encontrar ese \r en la memoria y va a creer que el usuario ya ha pulsado la tecla enter, cuando en realidad no ha tenido ocasión de pulsar nada.

En tu código, si volvemos a utilizar un único Scanner para todo el programa, es este el problema que ocurre.
Cuando falla el login y se le pregunta al usuario si quiere crear una nueva cuenta, estamos leyendo su respuesta con un nextInt().

Ese nextInt() va a dejar un \r en la memoria, en el flujo de datos del Scanner. Por eso en la siguiente iteración del bucle, donde un nextLine() espera recibir un "nombre de usuario", no se va a poder introducir porque ese \r "olvidado" va a finalizar la inserción del nombre sin tiempo a teclear nada.

Citar
public class Practica_1{
      public static void main(String args[]){
         int OPC_CrearCuenta = 0;
         boolean IntentoLogin = false;
         String Usuario, PassW, UsuarioL, PassWL;
         Scanner Input = new Scanner(System.in); //Un único Scanner
        
         do{
            //registro
            System.out.println("--- INICIO DE SESION ---\n\tRegistro");
            System.out.print("Nombre de ususario: ");
            Usuario = Input.nextLine(); //Este nextLine() fallará en la siguiente repetición
            System.out.print("Contraseña: ");
            PassW = Input.nextLine();
        
            //inicio
            System.out.println("--- INICIO DE SESION ---\n\tIniciar");
            System.out.print("Nombre de ususario: ");
            UsuarioL = Input.nextLine();
            System.out.print("Contraseña: ");
            PassWL = Input.nextLine();
        
        
            //ver si los datos coinciden
            if (UsuarioL.equals(Usuario) && PassWL.equals(PassW)){
               System.out.println("Has ingresado correctamente\n\tBienvenido " + Usuario);
               IntentoLogin = false;
            }else{
               System.out.println("Has ingresado el usuario de manera incorrecta");
               System.out.println("¿Deseas crear una nueva cuenta?\n1: SÍ | 2: NO");
               OPC_CrearCuenta = Input.nextInt(); //Estos nextInt() dejarán carácter \r en memoria que entorpecerán siguientes lecturas con nextLine()
            
               while(OPC_CrearCuenta > 2 || OPC_CrearCuenta < 1){ //mientras que el ususario no de la respuesta correcta
                  System.out.println("No has ingresado de la manera correcta. . .");
                  System.out.println("¿Deseas crear una nueva cuenta?\n1: SÍ | 2: NO");
                  OPC_CrearCuenta = Input.nextInt();
               }
               //en el caso de crear cuenta
               if (OPC_CrearCuenta == 1){
                  System.out.println("Has decidido crear una nueva cuenta. . .");
                  IntentoLogin = true;
               }else if (OPC_CrearCuenta == 2){
                  System.out.println("Has decidido no crear la cuenta. . .");
                  IntentoLogin = false;
               }
            }
         }while(IntentoLogin == true);
      }
   }


Podemos solucionarlo tal y como dije en el mensaje anterior. Hacemos todas las lecturas con nextLine(), para evitar dejar carácter \r olvidados.
Y si lo que necesitamos es un valor numérico, lo parseamos a Integer, a Double o a lo que haga falta.

Citar
public class Practica_1{
      public static void main(String args[]){
         int OPC_CrearCuenta = 0;
         boolean IntentoLogin = false;
         String Usuario, PassW, UsuarioL, PassWL;
         Scanner Input = new Scanner(System.in);
        
         do{
            //registro
            System.out.println("--- INICIO DE SESION ---\n\tRegistro");
            System.out.print("Nombre de ususario: ");
            Usuario = Input.nextLine();
            System.out.print("Contraseña: ");
            PassW = Input.nextLine();
        
            //inicio
            System.out.println("--- INICIO DE SESION ---\n\tIniciar");
            System.out.print("Nombre de ususario: ");
            UsuarioL = Input.nextLine();
            System.out.print("Contraseña: ");
            PassWL = Input.nextLine();
        
        
            //ver si los datos coinciden
            if (UsuarioL.equals(Usuario) && PassWL.equals(PassW)){
               System.out.println("Has ingresado correctamente\n\tBienvenido " + Usuario);
               IntentoLogin = false;
            }else{
               System.out.println("Has ingresado el usuario de manera incorrecta");
               System.out.println("¿Deseas crear una nueva cuenta?\n1: SÍ | 2: NO");
               OPC_CrearCuenta = Integer.parseInt(Input.nextLine());
               //Leemos un String con nextLine() y parseamos a Integer.
            
               while(OPC_CrearCuenta > 2 || OPC_CrearCuenta < 1){ //mientras que el ususario no de la respuesta correcta
                  System.out.println("No has ingresado de la manera correcta. . .");
                  System.out.println("¿Deseas crear una nueva cuenta?\n1: SÍ | 2: NO");
                  OPC_CrearCuenta = Integer.parseInt(Input.nextLine());
               }
               //en el caso de crear cuenta
               if (OPC_CrearCuenta == 1){
                  System.out.println("Has decidido crear una nueva cuenta. . .");
                  IntentoLogin = true;
               }else if (OPC_CrearCuenta == 2){
                  System.out.println("Has decidido no crear la cuenta. . .");
                  IntentoLogin = false;
               }
            }
         }while(IntentoLogin == true);
      }
   }


De este modo, con un único Scanner, podemos hacer lecturas de cualquier tipo tranquilamente.

Un saludo.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

CoduJ

  • Sin experiencia
  • *
  • Mensajes: 28
    • Ver Perfil
Re:El tema de los Scanners en Java
« Respuesta #4 en: 21 de Abril 2020, 03:44 »
Ah vale, ya entiendo. Con lo de "preferencia" me refería a que el programa tiene ya de manera "predeterminada" la entrada de un tipo de dato

Entonces, un objeto Scanner al querer usarlo de manera "universal", es probable que no tome en cuenta algunos valores, en este caso, con mi programa, se saltea la entrada del dato String, ya que la última entrada de datos fue con un valor int, ocasionando que al volver ocupar el Scanner se genere ese "retorno de carro".
« Última modificación: 25 de Octubre 2020, 20:47 por Alex Rodríguez »

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 737
    • Ver Perfil
Re:El tema de los Scanners en Java
« Respuesta #5 en: 21 de Abril 2020, 18:22 »
Sí.

Siempre que leas un dato numérico con nextInt(), nextDouble(), nextFloat(), nextByte()....  puedes estar seguro que la siguiente lectura con nextLine() fallará.

Por eso mejor hacer las lecturas como propuse, para evitar el problema.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

CoduJ

  • Sin experiencia
  • *
  • Mensajes: 28
    • Ver Perfil
Re:El tema de los Scanners en Java
« Respuesta #6 en: 22 de Abril 2020, 04:03 »
Sí.

Siempre que leas un dato numérico con nextInt(), nextDouble(), nextFloat(), nextByte()....  puedes estar seguro que la siguiente lectura con nextLine() fallará.

Por eso mejor hacer las lecturas como propuse, para evitar el problema.
¡Muchas gracias por la ayuda y el consejo!
Lamento seguir molestando con este tema, pero, ¿los Scanner influencian de alguna manera a que un programa tenga un mal rendimiento?

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 737
    • Ver Perfil
Re:El tema de los Scanners en Java
« Respuesta #7 en: 22 de Abril 2020, 16:54 »
No, para nada.

La clase Scanner también se puede usar para leer ficheros de texto, pero en este caso, puede ser preferible usar la clase BufferedReader que sí da un rendimiento más óptimo.

Pero vamos, hablamos de milisegundos, lo cuál no tiene que preocuparnos especialmente cuando estamos aprendiendo a programar, y no ha rascarle milisegundos al tiempo.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

 

Sobre la educación, sólo puedo decir que es el tema más importante en el que nosotros, como pueblo, debemos involucrarnos.

Abraham Lincoln (1808-1865) Presidente estadounidense.

aprenderaprogramar.com: Desde 2006 comprometidos con la didáctica y divulgación de la programación

Preguntas y respuestas

¿Cómo establecer o cambiar la imagen asociada (avatar) de usuario?
  1. Inicia sesión con tu nombre de usuario y contraseña.
  2. Pulsa en perfil --> perfil del foro
  3. Elige la imagen personalizada que quieras usar. Puedes escogerla de una galería de imágenes o subirla desde tu ordenador.
  4. En la parte final de la página pulsa el botón "cambiar perfil".