Cómo obtener la entrada del usuario en Java

En este artículo, usaremos las clases Scanner, BufferedReader e InputStreamReader para obtener información del usuario en Java. También implementaremos una clase InputStream personalizada para el procesamiento.

Introducción

Leer la entrada del usuario es el primer paso para escribir un software Java útil. La entrada del usuario puede venir en muchas formas: interacciones del mouse y el teclado, una solicitud de red, argumentos de línea de comandos, archivos que se actualizan con datos relevantes para la ejecución de un programa, etc.

Nos vamos a centrar en la entrada del teclado a través de algo llamado el flujo de entrada estándar. Puede reconocerlo como System.in de Java.

Vamos a utilizar la clase Scanner para facilitar nuestra interacción con el flujo subyacente. Dado que Scanner tiene algunas fallas, también usaremos las clases BufferedReader e InputStreamReader para procesar el flujo System.in.

Al final, decoraremos la clase InputStream e implementaremos nuestro propio UncloseableInputStream personalizado para manejar los problemas con la clase Scanner.

Clase de escáner Java

La clase java.util.Scanner es un escáner simple que puede analizar y manejar entradas primitivas, cadenas y flujos. Dado que System.in es solo un InputStream, podemos construir un Scanner como tal:

1
Scanner sc = new Scanner(System.in);

Esta instancia de Scanner ahora puede escanear y analizar booleanos, enteros, flotantes, bytes y cadenas.

Veamos cómo podemos extraer información de un Escáner en variables con las que podemos trabajar:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Scanner sc = new Scanner(System.in);
        
// Read an integer into a variable
int myInteger = sc.nextInt();
        
// Read a byte into a variable
byte myByte = sc.nextByte();
        
// Read a line until newline or EOF into a string
String myLine = sc.nextLine();
        
// Closing the scanner
sc.close();

Nuevamente, el constructor no tiene que tomar System.in. Puede tomar cualquier File, InputStream, Readable, ReadableByteChannel, Path (de un archivo para leer), o incluso String. Adicionalmente, como segundo argumento, puede especificar una codificación de caracteres para interpretar dichos caracteres por:

1
Scanner sc = new Scanner(new FileInputStream("myFile.txt"), "UTF-8");

Tenga en cuenta que Scanner debe cerrarse cuando haya terminado de trabajar con él. La forma más sencilla de hacerlo es a través de la sentencia prueba-con-recursos.

Métodos de escáner para leer

Los métodos disponibles para leer el siguiente token usando el método del escáner son:


Método Tipo de devolución Descripción next() String Encuentra y devuelve el siguiente token completo del escáner. nextByte() byte Explora el siguiente token de la entrada como un byte. nextDouble() double Escanea el siguiente token de la entrada como un doble. nextFloat() float Explora el siguiente token de la entrada como un flotante. nextInt() int Escanea el siguiente token de la entrada como un int. nextLong() long Explora el siguiente token de la entrada como largo. nextShort() short Escanea el siguiente token de la entrada como un short. nextBoolean() booleano Escanea el siguiente token de la entrada en un valor booleano y devuelve ese valor. nextLine() Cadena Avanza este escáner más allá de la línea actual y devuelve la entrada que se omitió.


Un método que vale la pena mencionar es el método hasNext(), un método genérico que devolverá true si hay algún tipo de token para leer. Hay métodos específicos de tipo, como hasNextInt(), hasNextFloat(), hasNextLine(), etc., que puede usar de la misma manera.

Problemas al usar System.in con el escáner

Un gran problema con System.in es que es un InputStream. Cuando se trabaja con él, el Scanner siempre esperará más entradas hasta que se cierre el InputStream. Una vez que se cierra la transmisión, ya no podemos acceder a la entrada del Scanner.

Además de cerrarse, la clase Scanner también cerrará InputStream si implementa Closeable.

Dado que InputStream lo hace, eso significa que Scanner cerrará el flujo System.in a la totalidad de su programa.

Dicho esto, si cierra un Scanner y, por lo tanto, System.in también, no podrá volver a usar System.in:

1
2
3
4
5
6
7
8
9
Scanner sc = new Scanner(System.in);
System.out.println(sc.nextInt());
sc.close();
System.out.println("Closing the scanner...");

sc = new Scanner(System.in);
System.out.println(sc.nextInt());
sc.close();
System.out.println("Closing the scanner...");

Esto resulta en:

1
2
3
4
5
6
7
8
9
1
1
Closing the scanner...
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 com.company.Main.main(Main.java:18)

Esto hace que el trabajo con Scanner y System.in sea mucho más complicado. Arreglaremos esto en la sección final.

Lector en búfer y Lector de flujo de entrada {#lector en búfer y lector de flujo de entrada}

En lugar de un Scanner, también puede usar un BufferedReader junto con un InputStreamReader para obtener la entrada del usuario:

1
2
3
4
5
6
7
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

String line;

while((line = br.readLine()) != null){
    System.out.println(String.format("The input is: %s", line));
}

Aquí, simplemente repetimos la cadena de entrada con un prefijo:

1
2
3
4
5
6
7
Hello!
The input is: Hello!
I'd like to order some extra large fries.
The input is: I'd like to order some extra large fries.
^D

Process finished with exit code 0

El BufferedReader es muy adecuado para leer cadenas, pero no tiene métodos integrados para manejar números. Para leer un número entero, tendría que analizarlo desde una cadena:

1
2
3
4
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

int a = Integer.parseInt(br.readLine());
System.out.println(a);

Esto funciona bien ahora:

1
2
5
5

Custom Uncloseable InputStream

Afortunadamente, existe una solución para que Scanner cierre la transmisión System.in gracias al Patrón de diseño de decorador. Podemos implementar nuestro propio InputStream y simplemente hacer que el método close() no haga nada para que cuando Scanner lo llame, no afecte la entrada estándar subyacente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class UnclosableInputStreamDecorator extends InputStream {

    private final InputStream inputStream;

    public UnclosableInputStreamDecorator(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    @Override
    public int read() throws IOException {
        return inputStream.read();
    }

    @Override
    public int read(byte[] b) throws IOException {
        return inputStream.read(b);
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        return inputStream.read(b, off, len);
    }

    @Override
    public long skip(long n) throws IOException {
        return inputStream.skip(n);
    }

    @Override
    public int available() throws IOException {
        return inputStream.available();
    }

    @Override
    public synchronized void mark(int readlimit) {
        inputStream.mark(readlimit);
    }

    @Override
    public synchronized void reset() throws IOException {
        inputStream.reset();
    }

    @Override
    public boolean markSupported() {
        return inputStream.markSupported();
    }

    @Override
    public void close() throws IOException {
        // Do nothing
    }
}

Cuando modificamos nuestro código problemático para usar el ‘InputStream’ personalizado, se ejecutará sin problemas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class ScannerDemo {
    public static void main(String[] args) {
    
        Scanner sc = new Scanner(new UnclosableInputStreamDecorator(System.in));
        System.out.println(sc.nextInt());
        sc.close();
        System.out.println("Closing the scanner...");

        sc = new Scanner(new UnclosableInputStreamDecorator(System.in));
        System.out.println(sc.nextInt());
        sc.close();
        System.out.println("Closing the scanner...");
    }
}

Ejecutar esto resultará en:

1
2
3
4
5
6
1
1
Closing the scanner...
1
1
Closing the scanner...

Conclusión

En este artículo, hemos cubierto cómo usar la clase Scanner para leer la entrada del usuario. Luego usamos la clase BufferedReader junto con InputStreamReader como un enfoque alternativo.

Finalmente, hemos implementado nuestro propio InputStream para evitar el problema de Scanner que cierra la secuencia System.in para todo el programa.

Con suerte, ha aprendido a manejar la entrada básica de la consola en Java y algunos errores comunes que puede encontrar en el camino.

Licensed under CC BY-NC-SA 4.0