Autor Tema: Duda lecturas desde teclado Java  (Leído 1529 veces)

drate

  • Sin experiencia
  • *
  • APR2.COM
  • Mensajes: 24
    • Ver Perfil
Duda lecturas desde teclado Java
« en: 27 de Junio 2023, 19:52 »
Buenas tardes.
Estoy aprendiendo este lenguaje y me ha surgido la duda de cuando es mejor emplear cada una de las variantes de Scanner.
Muchas gracias. Un saludo.

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 988
    • Ver Perfil
Re: Duda lecturas desde teclado Java
« Respuesta #1 en: 28 de Junio 2023, 01:47 »
No estoy muy seguro a que te refieres con "variantes".

Si te refieres a sus distintos métodos: nextInt(), nextDouble(), nextLine(), nextFloat(), etc...
Entonces, a priori, la elección dependerá del tipo de dato que necesites recibir por teclado.

Sin embargo, yo por lo general recomiendo hacer TODAS las lecturas con nextLine(), el cuál nos retorna un String, y ya luego parsear al tipo de dato que podamos necesitar.

Esto es porque nextLine() es el único método que hace una lectura completa de todo lo que haya en el buffer de entrada del Scanner, de manera que para la siguiente lectura el buffer ha quedado "limpio".
Los otros métodos cogen solo lo que necesitan para construir el tipo de dato que han de retornar y dejan "restos" en el buffer, los cuáles pueden entorpecer las siguientes lecturas.

Por ejemplo, en un mensaje anterior te compartí un código para leer un double y controlar como excepción la posibilidad de que fuera negativo.
En el método donde hacemos la lectura de ese double, habrás visto que hago la lectura con nextLine() y al mismo tiempo parseo el String que me proporciona a double:
Citar
   private static double pedirDoublePositivo() throws Exception {
      
      System.out.print("\nIntroduzca un valor double positivo: ");
      double valor = Double.parseDouble(teclado.nextLine());
      
      if (valor < 0)
         throw new Exception("El valor " + valor + " no es positivo.");
      else
         return valor;
   }
Parece que estoy complicando las cosas innecesariamente, pues podría haber hecho la lectura directamente con nextDouble():
Citar
   private static double pedirDoublePositivo() throws Exception {
      
      System.out.print("\nIntroduzca un valor double positivo: ");
      double valor = teclado.nextDouble();
      
      if (valor < 0)
         throw new Exception("El valor " + valor + " no es positivo.");
      else
         return valor;
   }

Y en principio funciona igual de una forma que la otra.
Sin embargo, si forzamos ha cometer un error con la forma en que yo lo hice y tecleamos un texto en lugar de un valor numérico, se produce una excepción, que va a quedar controlada y no pasa nada, el programa continua:
Citar
Introduzca un valor double positivo: catorce
ERROR. Dato invalido:
For input string: "catorce"

Introduzca un valor double positivo: 14

Valor introducido: 14.0

      FIN DE PROGRAMA


Pero, si forzamos el mismo error, esta vez haciendo la lectura con nextDouble(), resulta que el programa queda bloqueado en un bucle infinito debido a que está recibiendo un valor null sin que nos de pie a poder teclear nada distinto.
Y hay que forzar el cierre del programa a las bravas:
Código: [Seleccionar]
Introduzca un valor double positivo: catorce
ERROR. Dato invalido:
For input string: "catorce"

Introduzca un valor double positivo: ERROR. Dato invalido:
null

Introduzca un valor double positivo: ERROR. Dato invalido:
null

Introduzca un valor double positivo: ERROR. Dato invalido:
null

Introduzca un valor double positivo: ERROR. Dato invalido:
null

Introduzca un valor double positivo: ERROR. Dato invalido:
null

Introduzca un valor double positivo: ERROR. Dato invalido:
null

Introduzca un valor double positivo: ERROR. Dato invalido:
null

Introduzca un valor double positivo: ERROR. Dato invalido:
null

Introduzca un valor double positivo: ERROR. Dato invalido:
null

Introduzca un valor double positivo: ERROR. Dato invalido:
null

Introduzca un valor double positivo: ERROR. Dato invalido:
null

Introduzca un valor double positivo: ERROR. Dato invalido:
null

Introduzca un valor double positivo: ERROR. Dato invalido:
null

Introduzca un valor double positivo: ERROR. Dato invalido:
null

¿Por qué?¿A que se debe esta diferencia de comportamiento?

Recuerda que te mencioné anteriormente un poco sobre los "caracteres especiales". Caracteres que no tienen representación gráfica en pantalla, si no que conllevan una acción.
Por ejemplo el carácter de "nueva línea", el "\n". O también el carácter para tabular texto, el "\t".

Hay otro carácter llamado "retorno de carro", que en lenguaje C y en sus parientes (Java es pariente de C) se representa como "\r".

Es el carácter que pone fin a una línea y hace pasar a la siguiente, como la palanca de las viejas máquinas de escribir.
Este carácter, aunque no lo veamos, se introduce en el Scanner cada vez que pulsamos la tecla intro/enter de nuestro teclado cuando introducimos datos.

Volviendo al ejemplo anterior, cuando yo fuerzo el error tecleando el valor "catorce", en realidad en el buffer del Scanner lo que entra es "catorce\r"

Si le pedimos a nextDouble() que intenta conformar un valor double con esos caracteres, no lo va a conseguir porque el busca caracteres numéricos, del 0 al 9.
Así que se produce una excepción, la cuál controlamos y bueno, no es tan grave.

El problema está en que el buffer del Scanner, no ha quedado limpio.
Los caracteres que componen el String "catorce", han sido recogidos y rechazados por nextDouble(). Pero nextDouble() no captura "caracteres especiales", así que en el buffer del Scanner aún tenemos el carácter "\r"
¿Qué ocurre ahora?
Que al volver a pedir el dato, nextDouble() detecta de nuevo el carácter "\r" y erróneamente piensa que el usuario ha vuelto a pulsar la tecla intro/enter que da por finalizada la lectura de datos.
Así que sin que el usuario haya podido hacer nada realmente, nextDouble() intenta crear de nuevo un dato, pero realmente no hay nada (valor null), así que de nuevo excepción, el buffer sigue teniendo "\r", el bucle se repite, nextDouble() de nuevo encuentra valor null, excepción, el buffer sigue conservando "\r",....
así que el flujo del programa queda atrapado en un bucle sin fin porque nextDouble() reconoce el carácter "\r" pero no lo recoge, así que ese carácter se queda "enquistado" en el buffer del Scanner para siempre.

Esto mismo ocurre con nextInt(), nextShort(), nextByte(), nextFloat().....
Con todos, excepto con nextLine().

nextLine() es el único que se "traga" cualquier cosa que tenga el buffer del Scanner, así que tras cada lectura el buffer va a quedar limpio, sin ningún resto que pueda afectar a las siguientes lecturas.

Por eso conviene leer siempre con nextLine(), y luego hacer el parseo a lo que se necesite.
Puede que ese proceso de parseo luego provoque una excepción o no, pero en cualquier caso, el buffer del Scanner habrá quedado limpio.

Esto ocurre no solo en caso de posibles excepciones. Una lectura que haya resultado correcta con nextDouble() u otro de estos métodos, luego puede entorpecer también futuras lecturas.

Veamos un ejemplo super sencillo:
Pedimos primero el nombre y luego la edad.
El nombre lo leemos con nextLine() porque queremos un String y el edad con nextInt() porque queremos un int.
Código: [Seleccionar]
public class Ejemplo {

public static void main(String[] args) {
Scanner teclado = new Scanner(System.in);

System.out.print("Dime tu nombre: ");
String nombre = teclado.nextLine();

System.out.print("Dime tu edad: ");
int edad = teclado.nextInt();

System.out.println("\nTe llamas " + nombre + " y tu edad es " + edad);
teclado.close();

}

}

Si probamos ese código, funciona perfectamente, como no podría ser de otra forma.
Pero invirtamos la petición de datos, primero pedimos la edad y luego el nombre
Código: [Seleccionar]
public class Ejemplo {

public static void main(String[] args) {
Scanner teclado = new Scanner(System.in);

System.out.print("Dime tu edad: ");
int edad = teclado.nextInt();

System.out.print("Dime tu nombre: ");
String nombre = teclado.nextLine();

System.out.println("\nTe llamas " + nombre + " y tu edad es " + edad);
teclado.close();

}

}

 :o ¡¡Ohh!! Resulta que tras introducir la edad, no nos deja introducir el nombre y el programa termina como si hubiéramos introducido una cadena vacía para el nombre.
¿Qué ha ocurrido?
Lo explicado anteriormente.
nextInt() ha cogido lo necesario para fabricar un int, pero ha dejado el carácter "\r" en el buffer.
Luego, nextLine() ha querido leer un nombre, pero al encontrarse un "\r" en el buffer, da por hecho que el usuario ha pulsado la tecla enter así que da por finalizada la lectura a pesar de que solo obtiene una cadena vacía, porque el usuario en realidad no ha tenido ocasión de teclear nada.

Para evitar estos problemas, lo mejor es leer todo con nextLine().
Y si lo que necesitamos es un int, pues hacemos un parseo mediante la clase Integer
Código: [Seleccionar]
public class Ejemplo {

public static void main(String[] args) {
Scanner teclado = new Scanner(System.in);

System.out.print("Dime tu edad: ");
int edad = Integer.parseInt(teclado.nextLine());

System.out.print("Dime tu nombre: ");
String nombre = teclado.nextLine();

System.out.println("\nTe llamas " + nombre + " y tu edad es " + edad);
teclado.close();

}

}

Espero haber aclarado tus dudas. Si no es así, o bien, todo esto hace que surjan nuevas dudas distintas, solo tienes que preguntar.

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

 

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".