Java
Tutorial Java: La clase Scanner
Java scanner: entrada de datos. Domina el uso de la clase Scanner en Java para la entrada de datos con ejemplos detallados.
Aprende Java y certifícateFundamentos y construcción de objetos Scanner
La clase Scanner
sirve para procesar la entrada de datos desde diversas fuentes. Se encuentra en el paquete java.util
y proporciona métodos para leer y analizar datos primitivos y cadenas de texto. Esta clase actúa como un puente entre las fuentes de datos y nuestro programa, permitiendo la lectura estructurada de información.
Para utilizar la clase Scanner en cualquier programa Java, primero se debe importar:
import java.util.Scanner;
La clase Scanner utiliza un concepto llamado tokenización, que consiste en dividir la entrada en unidades lógicas llamadas tokens, utilizando delimitadores. Por defecto, el delimitador es el espacio en blanco, pero se puede personalizar según las necesidades específicas del programa.
Constructores de Scanner
La versatilidad de Scanner se refleja en sus múltiples constructores, que permiten crear objetos a partir de diferentes fuentes de datos:
- Entrada estándar (teclado):
Scanner teclado = new Scanner(System.in);
- Cadenas de texto:
String texto = "Hola mundo 123";
Scanner lector = new Scanner(texto);
- Archivos:
import java.io.File;
import java.io.FileNotFoundException;
try {
File archivo = new File("datos.txt");
Scanner lectorArchivo = new Scanner(archivo);
} catch (FileNotFoundException e) {
System.err.println("No se encontró el archivo");
}
- Streams personalizados:
import java.io.ByteArrayInputStream;
String datos = "10 20 30";
ByteArrayInputStream stream = new ByteArrayInputStream(datos.getBytes());
Scanner lectorStream = new Scanner(stream);
Configuración del delimitador
El comportamiento de tokenización se puede personalizar mediante la configuración de delimitadores. Por defecto, Scanner utiliza espacios en blanco, pero se puede modificar usando el método useDelimiter()
:
String csvData = "Juan,25,Madrid";
Scanner csvScanner = new Scanner(csvData);
csvScanner.useDelimiter(",");
// Ahora cada valor separado por coma será un token diferente
String nombre = csvScanner.next(); // "Juan"
int edad = csvScanner.nextInt(); // 25
String ciudad = csvScanner.next(); // "Madrid"
También se pueden utilizar expresiones regulares como delimitadores para casos más complejos:
Scanner scanner = new Scanner("datos: 15; valor: 23");
scanner.useDelimiter("[;:]\\s*"); // Delimita por ";" o ":" seguidos de espacios opcionales
String palabra1 = scanner.next(); // "datos"
int numero1 = scanner.nextInt(); // 15
String palabra2 = scanner.next(); // "valor"
int numero2 = scanner.nextInt(); // 23
Configuración de la localización
La interpretación de números y formatos puede variar según la región. Scanner permite configurar la localización para adaptarse a diferentes formatos regionales:
import java.util.Locale;
// Crear un Scanner con localización española
Scanner scannerES = new Scanner(System.in);
scannerES.useLocale(new Locale("es", "ES"));
// Crear un Scanner con localización estadounidense
Scanner scannerUS = new Scanner(System.in);
scannerUS.useLocale(Locale.US);
// Importante: en español se usa coma para decimales, en inglés punto
// 3,14 (español) vs 3.14 (inglés)
Verificación de disponibilidad de datos
Antes de leer datos, es recomendable verificar si existen tokens disponibles del tipo esperado para evitar excepciones:
Scanner scanner = new Scanner("texto 123 true");
// Verificar si hay un String disponible
if (scanner.hasNext()) {
String texto = scanner.next();
System.out.println("Texto: " + texto);
}
// Verificar si hay un entero disponible
if (scanner.hasNextInt()) {
int numero = scanner.nextInt();
System.out.println("Número: " + numero);
}
// Verificar si hay un booleano disponible
if (scanner.hasNextBoolean()) {
boolean valor = scanner.nextBoolean();
System.out.println("Booleano: " + valor);
}
Ejemplo práctico: Creación de un formulario simple
Veamos un ejemplo completo que integra varios conceptos para crear un formulario básico de entrada de datos:
import java.util.Scanner;
public class Formulario {
public static void main(String[] args) {
// Crear Scanner para leer desde el teclado
Scanner entrada = new Scanner(System.in);
// Solicitar y leer nombre (String)
System.out.print("Introduce tu nombre: ");
String nombre = entrada.nextLine();
// Solicitar y leer edad (int)
System.out.print("Introduce tu edad: ");
int edad = 0;
// Verificar que se ingrese un entero válido
if (entrada.hasNextInt()) {
edad = entrada.nextInt();
entrada.nextLine(); // Limpiar el buffer
} else {
System.out.println("Edad no válida, se asignará 0");
entrada.nextLine(); // Limpiar el buffer
}
// Solicitar y leer si tiene experiencia (boolean)
System.out.print("¿Tienes experiencia previa? (true/false): ");
boolean experiencia = false;
if (entrada.hasNextBoolean()) {
experiencia = entrada.nextBoolean();
}
// Mostrar resumen de datos
System.out.println("\n--- Datos del formulario ---");
System.out.println("Nombre: " + nombre);
System.out.println("Edad: " + edad);
System.out.println("Experiencia previa: " + experiencia);
// Cerrar el Scanner
entrada.close();
}
}
Consideraciones sobre el ciclo de vida
Es importante gestionar adecuadamente el ciclo de vida de los objetos Scanner. Cuando ya no se necesita un Scanner, se debe cerrar para liberar los recursos asociados:
Scanner scanner = new Scanner(System.in);
// Operaciones con el scanner
scanner.close();
Sin embargo, hay que tener precaución al cerrar Scanner cuando se utiliza con System.in
, ya que cerrar el Scanner también cerrará la entrada estándar, lo que podría afectar a otros Scanner que se creen posteriormente. En aplicaciones complejas, se suele crear un único Scanner para System.in
que se mantiene durante toda la ejecución del programa.
Integración con buffers y streams
Scanner se integra perfectamente con el sistema de entrada/salida de Java, permitiendo la lectura de datos desde cualquier objeto que implemente la interfaz Readable
o cualquier InputStream
:
import java.io.BufferedReader;
import java.io.StringReader;
// Crear un BufferedReader y pasarlo a Scanner
BufferedReader buffer = new BufferedReader(new StringReader("línea 1\nlínea 2\nlínea 3"));
Scanner scannerBuffer = new Scanner(buffer);
// Leer línea por línea
while (scannerBuffer.hasNextLine()) {
System.out.println(scannerBuffer.nextLine());
}
scannerBuffer.close();
Lectura de diferentes tipos de datos
La clase Scanner
puede leer y convertir automáticamente diferentes tipos de datos desde una fuente de entrada. Esta versatilidad permite procesar información variada sin necesidad de realizar conversiones manuales, lo que simplifica considerablemente el código de entrada de datos en aplicaciones Java.
Métodos de lectura básicos
La clase Scanner
proporciona métodos específicos para cada tipo de dato primitivo y para cadenas de texto. Cada método se encarga de leer el siguiente token disponible y convertirlo al tipo correspondiente:
import java.util.Scanner;
public class LecturaDatos {
public static void main(String[] args) {
String entrada = "42 3.14 true Hola";
Scanner scanner = new Scanner(entrada);
int entero = scanner.nextInt(); // Lee "42" como entero
double decimal = scanner.nextDouble(); // Lee "3.14" como double
boolean logico = scanner.nextBoolean(); // Lee "true" como boolean
String texto = scanner.next(); // Lee "Hola" como String
System.out.println("Entero: " + entero);
System.out.println("Decimal: " + decimal);
System.out.println("Lógico: " + logico);
System.out.println("Texto: " + texto);
scanner.close();
}
}
Lectura de líneas completas
Cuando se necesita capturar una línea entera, incluyendo espacios, se utiliza el método nextLine()
:
Scanner scanner = new Scanner(System.in);
System.out.print("Introduce una frase: ");
String lineaCompleta = scanner.nextLine();
System.out.println("Has escrito: " + lineaCompleta);
nextLine()
consume el salto de línea pendiente, lo que puede causar comportamientos inesperados cuando se combina con otros métodos de lectura:
Scanner scanner = new Scanner(System.in);
System.out.print("Introduce tu edad: ");
int edad = scanner.nextInt();
// El salto de línea queda en el buffer
scanner.nextLine(); // Se consume el salto de línea pendiente
System.out.print("Introduce tu nombre completo: ");
String nombre = scanner.nextLine();
System.out.println("Nombre: " + nombre + ", Edad: " + edad);
Lectura de tipos numéricos
La clase Scanner
ofrece métodos específicos para cada tipo numérico en Java:
Scanner scanner = new Scanner("10 20 30.5 40.75 9223372036854775807 3.4028235E38");
byte valorByte = scanner.nextByte(); // Lee un byte (8 bits)
short valorShort = scanner.nextShort(); // Lee un short (16 bits)
float valorFloat = scanner.nextFloat(); // Lee un float
double valorDouble = scanner.nextDouble(); // Lee un double
long valorLong = scanner.nextLong(); // Lee un long (64 bits)
float valorFloatMax = scanner.nextFloat(); // Lee un float grande
System.out.println("byte: " + valorByte);
System.out.println("short: " + valorShort);
System.out.println("float: " + valorFloat);
System.out.println("double: " + valorDouble);
System.out.println("long: " + valorLong);
System.out.println("float máximo: " + valorFloatMax);
Lectura con formato específico
Para situaciones donde los datos tienen un formato particular, se puede utilizar el método findInLine()
junto con expresiones regulares:
String datos = "Código: ABC-123, Precio: 29.99€";
Scanner scanner = new Scanner(datos);
// Buscar un patrón de código (letras-números)
String codigo = scanner.findInLine("[A-Z]+-\\d+");
System.out.println("Código encontrado: " + codigo);
// Buscar un valor numérico seguido de símbolo de euro
scanner.findInLine("Precio: ");
double precio = scanner.nextDouble();
System.out.println("Precio: " + precio);
Lectura de datos con validación
En aplicaciones reales, es común necesitar validar los datos de entrada. Se puede combinar los métodos hasNext
con la lectura para crear entradas robustas:
import java.util.Scanner;
public class EntradaValidada {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int edad = 0;
boolean entradaValida = false;
while (!entradaValida) {
System.out.print("Introduce tu edad (1-120): ");
if (scanner.hasNextInt()) {
edad = scanner.nextInt();
if (edad >= 1 && edad <= 120) {
entradaValida = true;
} else {
System.out.println("La edad debe estar entre 1 y 120.");
}
} else {
System.out.println("Por favor, introduce un número válido.");
scanner.next(); // Descartar la entrada inválida
}
}
System.out.println("Edad registrada: " + edad);
scanner.close();
}
}
Lectura de datos desde archivos CSV
La clase Scanner
es útil para procesar archivos con formato estructurado como CSV:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class LectorCSV {
public static void main(String[] args) {
try {
File archivo = new File("datos.csv");
Scanner scanner = new Scanner(archivo);
// Configurar coma como delimitador
scanner.useDelimiter(",|\\n");
// Leer y procesar encabezados
String nombre = scanner.next();
String edadHeader = scanner.next();
String salarioHeader = scanner.next();
System.out.println("Datos de empleados:");
System.out.printf("%-15s %-5s %-10s%n", nombre, edadHeader, salarioHeader);
System.out.println("------------------------------");
// Procesar cada línea de datos
while (scanner.hasNext()) {
String nombreEmpleado = scanner.next();
int edadEmpleado = scanner.nextInt();
double salarioEmpleado = scanner.nextDouble();
System.out.printf("%-15s %-5d %.2f€%n",
nombreEmpleado, edadEmpleado, salarioEmpleado);
}
scanner.close();
} catch (FileNotFoundException e) {
System.err.println("No se pudo encontrar el archivo: " + e.getMessage());
}
}
}
Lectura de datos binarios
Aunque Scanner
está principalmente diseñado para texto, también puede leer datos binarios utilizando métodos como nextByte()
junto con la configuración adecuada:
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Scanner;
public class LecturaBinaria {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("datos.bin");
Scanner scanner = new Scanner(fis)) {
// Configurar para leer bytes individuales
scanner.useRadix(16); // Base hexadecimal
System.out.println("Contenido del archivo en hexadecimal:");
while (scanner.hasNextInt()) {
int byteLeido = scanner.nextInt();
System.out.printf("%02X ", byteLeido);
}
} catch (IOException e) {
System.err.println("Error al leer el archivo: " + e.getMessage());
}
}
}
Lectura de datos con formato específico de localización
Para aplicaciones internacionales, es crucial manejar correctamente los formatos numéricos según la localización:
import java.util.Locale;
import java.util.Scanner;
public class LecturaInternacional {
public static void main(String[] args) {
// Datos con formato español (coma decimal)
String datosES = "3,14 2,718 1,618";
Scanner scannerES = new Scanner(datosES);
scannerES.useLocale(new Locale("es", "ES"));
// Datos con formato inglés (punto decimal)
String datosEN = "3.14 2.718 1.618";
Scanner scannerEN = new Scanner(datosEN);
scannerEN.useLocale(Locale.US);
System.out.println("Leyendo números con formato español:");
while (scannerES.hasNextDouble()) {
System.out.println(scannerES.nextDouble());
}
System.out.println("\nLeyendo números con formato inglés:");
while (scannerEN.hasNextDouble()) {
System.out.println(scannerEN.nextDouble());
}
scannerES.close();
scannerEN.close();
}
}
Manejo de errores y excepciones
Al trabajar con la clase Scanner
en Java, el manejo adecuado de errores y excepciones es fundamental para crear aplicaciones robustas que puedan responder correctamente ante situaciones imprevistas. Cuando se procesan datos de entrada, especialmente aquellos proporcionados por usuarios o fuentes externas, es común encontrarse con valores que no cumplen con el formato esperado.
La clase Scanner
puede lanzar diversas excepciones durante su operación, siendo las más comunes:
InputMismatchException
: Se produce cuando el token leído no coincide con el patrón esperado para el tipo de dato solicitado.NoSuchElementException
: Ocurre al intentar leer más allá del final de la entrada.IllegalStateException
: Se lanza si el scanner ha sido cerrado antes de realizar una operación.FileNotFoundException
: Aparece cuando se intenta crear un Scanner con un archivo que no existe.
Captura de InputMismatchException
Uno de los errores más frecuentes al utilizar Scanner
es intentar leer un tipo de dato cuando la entrada contiene un formato incompatible:
import java.util.InputMismatchException;
import java.util.Scanner;
public class ManejoInputMismatch {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("Introduce tu edad: ");
try {
int edad = scanner.nextInt();
System.out.println("Tu edad es: " + edad);
} catch (InputMismatchException e) {
System.out.println("Error: Debes introducir un número entero.");
// La entrada inválida permanece en el buffer
String entradaInvalida = scanner.next(); // Limpia el buffer
System.out.println("Entrada rechazada: " + entradaInvalida);
}
scanner.close();
}
}
Cuando se produce una InputMismatchException
, el token que causó el error permanece en el buffer de entrada. Por ello, es necesario consumirlo explícitamente con scanner.next()
para evitar un bucle infinito de excepciones si se intenta leer nuevamente.
Manejo de NoSuchElementException
Esta excepción se produce cuando intentamos leer más allá del final de la entrada disponible:
import java.util.NoSuchElementException;
import java.util.Scanner;
public class ManejoNoSuchElement {
public static void main(String[] args) {
Scanner scanner = new Scanner("Solo hay una línea de texto");
try {
// Leemos la primera línea correctamente
String primeraLinea = scanner.nextLine();
System.out.println("Primera línea: " + primeraLinea);
// Intentamos leer una segunda línea que no existe
String segundaLinea = scanner.nextLine();
System.out.println("Segunda línea: " + segundaLinea);
} catch (NoSuchElementException e) {
System.out.println("Error: No hay más líneas para leer.");
} finally {
scanner.close();
}
}
}
Para evitar esta excepción, se recomienda utilizar los métodos hasNext()
, hasNextLine()
o similares antes de intentar leer datos:
Scanner scanner = new Scanner("Datos limitados");
while (scanner.hasNextLine()) {
String linea = scanner.nextLine();
System.out.println("Línea leída: " + linea);
}
scanner.close();
Manejo de IllegalStateException
Esta excepción ocurre cuando se intenta utilizar un Scanner
que ya ha sido cerrado:
import java.util.IllegalStateException;
import java.util.Scanner;
public class ManejoIllegalState {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Escribe algo: ");
String entrada = scanner.nextLine();
System.out.println("Has escrito: " + entrada);
scanner.close(); // Cerramos el scanner
try {
System.out.println("Escribe algo más: ");
String masEntrada = scanner.nextLine(); // Esto lanzará IllegalStateException
} catch (IllegalStateException e) {
System.out.println("Error: El scanner ya ha sido cerrado.");
System.out.println("Mensaje de error: " + e.getMessage());
}
}
}
Para evitar este problema, se debe mantener el Scanner
abierto mientras sea necesario y cerrarlo solo cuando ya no se vaya a utilizar más.
Manejo de FileNotFoundException
Cuando se crea un Scanner
para leer un archivo, es obligatorio manejar la posible FileNotFoundException
:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ManejoFileNotFound {
public static void main(String[] args) {
try {
File archivo = new File("datos_inexistentes.txt");
Scanner scanner = new Scanner(archivo);
while (scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
scanner.close();
} catch (FileNotFoundException e) {
System.out.println("Error: No se pudo encontrar el archivo.");
System.out.println("Ruta: " + e.getMessage());
// Ofrecer alternativas al usuario
System.out.println("¿Deseas crear el archivo? (s/n)");
Scanner consola = new Scanner(System.in);
String respuesta = consola.nextLine();
if (respuesta.equalsIgnoreCase("s")) {
// Código para crear el archivo
System.out.println("Implementación de creación de archivo...");
}
consola.close();
}
}
}
Patrón de validación con bucle
Un enfoque común para manejar errores de entrada es implementar un bucle de validación que continúe solicitando datos hasta recibir una entrada válida:
import java.util.InputMismatchException;
import java.util.Scanner;
public class ValidacionConBucle {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int numero = 0;
boolean entradaValida = false;
while (!entradaValida) {
System.out.print("Introduce un número entero: ");
try {
numero = scanner.nextInt();
entradaValida = true; // Si llegamos aquí, la entrada es válida
} catch (InputMismatchException e) {
System.out.println("Error: Debes introducir un número entero.");
scanner.next(); // Limpiar la entrada inválida
}
}
System.out.println("Has introducido: " + numero);
scanner.close();
}
}
Validación con métodos específicos
Para casos más complejos, se pueden crear métodos de validación específicos que encapsulen la lógica de manejo de errores:
import java.util.InputMismatchException;
import java.util.Scanner;
public class ValidacionMetodos {
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
int edad = leerEnteroEnRango("Introduce tu edad: ", 0, 120);
double altura = leerDecimalEnRango("Introduce tu altura (en metros): ", 0.5, 2.5);
String nombre = leerTextoNoVacio("Introduce tu nombre: ");
System.out.println("\nDatos registrados:");
System.out.println("Nombre: " + nombre);
System.out.println("Edad: " + edad + " años");
System.out.println("Altura: " + altura + " metros");
scanner.close();
}
/**
* Lee un entero dentro de un rango específico.
* @param mensaje Mensaje para solicitar el dato
* @param min Valor mínimo aceptable
* @param max Valor máximo aceptable
* @return Entero validado dentro del rango
*/
public static int leerEnteroEnRango(String mensaje, int min, int max) {
int valor = 0;
boolean entradaValida = false;
while (!entradaValida) {
System.out.print(mensaje);
try {
valor = scanner.nextInt();
scanner.nextLine(); // Limpiar buffer
if (valor >= min && valor <= max) {
entradaValida = true;
} else {
System.out.printf("Error: El valor debe estar entre %d y %d.%n", min, max);
}
} catch (InputMismatchException e) {
System.out.println("Error: Debes introducir un número entero.");
scanner.nextLine(); // Limpiar la entrada inválida
}
}
return valor;
}
/**
* Lee un decimal dentro de un rango específico.
* @param mensaje Mensaje para solicitar el dato
* @param min Valor mínimo aceptable
* @param max Valor máximo aceptable
* @return Decimal validado dentro del rango
*/
public static double leerDecimalEnRango(String mensaje, double min, double max) {
double valor = 0;
boolean entradaValida = false;
while (!entradaValida) {
System.out.print(mensaje);
try {
valor = scanner.nextDouble();
scanner.nextLine(); // Limpiar buffer
if (valor >= min && valor <= max) {
entradaValida = true;
} else {
System.out.printf("Error: El valor debe estar entre %.2f y %.2f.%n", min, max);
}
} catch (InputMismatchException e) {
System.out.println("Error: Debes introducir un número decimal.");
scanner.nextLine(); // Limpiar la entrada inválida
}
}
return valor;
}
/**
* Lee una cadena de texto no vacía.
* @param mensaje Mensaje para solicitar el dato
* @return Texto validado no vacío
*/
public static String leerTextoNoVacio(String mensaje) {
String texto = "";
boolean entradaValida = false;
while (!entradaValida) {
System.out.print(mensaje);
texto = scanner.nextLine().trim();
if (!texto.isEmpty()) {
entradaValida = true;
} else {
System.out.println("Error: El texto no puede estar vacío.");
}
}
return texto;
}
}
Manejo de recursos con try-with-resources
Para garantizar que los recursos se cierren correctamente incluso en caso de excepción, se recomienda utilizar el patrón try-with-resources introducido en Java 7:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class TryWithResourcesScanner {
public static void main(String[] args) {
File archivo = new File("datos.txt");
try (Scanner scanner = new Scanner(archivo)) {
while (scanner.hasNextLine()) {
String linea = scanner.nextLine();
System.out.println(linea);
}
// No es necesario cerrar el scanner, se cierra automáticamente
} catch (FileNotFoundException e) {
System.out.println("Error: No se pudo encontrar el archivo.");
e.printStackTrace();
}
}
}
Manejo de errores en aplicaciones interactivas
En aplicaciones interactivas más complejas, es útil implementar un sistema de manejo de errores que proporcione retroalimentación clara al usuario:
import java.util.InputMismatchException;
import java.util.Scanner;
public class AplicacionInteractiva {
public static void main(String[] args) {
try (Scanner scanner = new Scanner(System.in)) {
boolean ejecutando = true;
while (ejecutando) {
mostrarMenu();
try {
System.out.print("Selecciona una opción: ");
int opcion = scanner.nextInt();
scanner.nextLine(); // Limpiar buffer
switch (opcion) {
case 1:
procesarDatosPersonales(scanner);
break;
case 2:
calcularEstadisticas(scanner);
break;
case 3:
System.out.println("Saliendo del programa...");
ejecutando = false;
break;
default:
System.out.println("Opción no válida. Intenta de nuevo.");
}
} catch (InputMismatchException e) {
System.out.println("Error: Debes introducir un número.");
scanner.nextLine(); // Limpiar la entrada inválida
} catch (Exception e) {
System.out.println("Error inesperado: " + e.getMessage());
e.printStackTrace();
}
System.out.println(); // Línea en blanco para separar iteraciones
}
} // Scanner se cierra automáticamente
}
private static void mostrarMenu() {
System.out.println("=== MENÚ PRINCIPAL ===");
System.out.println("1. Introducir datos personales");
System.out.println("2. Calcular estadísticas");
System.out.println("3. Salir");
}
private static void procesarDatosPersonales(Scanner scanner) {
System.out.println("\n-- Formulario de Datos Personales --");
try {
String nombre = leerTextoNoVacio(scanner, "Nombre: ");
int edad = leerEnteroPositivo(scanner, "Edad: ");
System.out.println("\nDatos registrados correctamente:");
System.out.println("Nombre: " + nombre);
System.out.println("Edad: " + edad);
} catch (Exception e) {
System.out.println("Error al procesar datos personales: " + e.getMessage());
}
}
private static void calcularEstadisticas(Scanner scanner) {
System.out.println("\n-- Calculadora de Estadísticas --");
try {
System.out.print("¿Cuántos números deseas introducir? ");
int cantidad = scanner.nextInt();
if (cantidad <= 0) {
System.out.println("La cantidad debe ser positiva.");
return;
}
double suma = 0;
double maximo = Double.MIN_VALUE;
double minimo = Double.MAX_VALUE;
for (int i = 1; i <= cantidad; i++) {
System.out.print("Número " + i + ": ");
double numero = scanner.nextDouble();
suma += numero;
maximo = Math.max(maximo, numero);
minimo = Math.min(minimo, numero);
}
double promedio = suma / cantidad;
System.out.println("\nResultados:");
System.out.printf("Promedio: %.2f%n", promedio);
System.out.printf("Máximo: %.2f%n", maximo);
System.out.printf("Mínimo: %.2f%n", minimo);
} catch (InputMismatchException e) {
System.out.println("Error: Formato de número inválido.");
scanner.nextLine(); // Limpiar buffer
} catch (Exception e) {
System.out.println("Error en el cálculo: " + e.getMessage());
}
}
private static String leerTextoNoVacio(Scanner scanner, String prompt) {
String texto;
do {
System.out.print(prompt);
texto = scanner.nextLine().trim();
if (texto.isEmpty()) {
System.out.println("Error: El texto no puede estar vacío.");
}
} while (texto.isEmpty());
return texto;
}
private static int leerEnteroPositivo(Scanner scanner, String prompt) {
int valor = 0;
boolean entradaValida = false;
while (!entradaValida) {
try {
System.out.print(prompt);
valor = scanner.nextInt();
scanner.nextLine(); // Limpiar buffer
if (valor > 0) {
entradaValida = true;
} else {
System.out.println("Error: El valor debe ser positivo.");
}
} catch (InputMismatchException e) {
System.out.println("Error: Debes introducir un número entero.");
scanner.nextLine(); // Limpiar la entrada inválida
}
}
return valor;
}
}
Recuperación de errores en procesamiento por lotes
Cuando se procesan grandes cantidades de datos, es importante implementar estrategias de recuperación que permitan continuar con el procesamiento incluso después de encontrar errores:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner;
public class ProcesadorPorLotes {
public static void main(String[] args) {
File archivo = new File("datos_numericos.txt");
List<Integer> numerosValidos = new ArrayList<>();
List<String> errores = new ArrayList<>();
int lineasProcesadas = 0;
try (Scanner scanner = new Scanner(archivo)) {
while (scanner.hasNextLine()) {
lineasProcesadas++;
String linea = scanner.nextLine().trim();
if (linea.isEmpty()) {
continue; // Ignorar líneas vacías
}
try {
Scanner lineScanner = new Scanner(linea);
int numero = lineScanner.nextInt();
if (lineScanner.hasNext()) {
errores.add("Línea " + lineasProcesadas + ": Datos adicionales después del número");
} else {
numerosValidos.add(numero);
}
lineScanner.close();
} catch (InputMismatchException e) {
errores.add("Línea " + lineasProcesadas + ": No es un número válido - '" + linea + "'");
}
}
} catch (FileNotFoundException e) {
System.out.println("Error: No se pudo encontrar el archivo.");
return;
}
// Mostrar resultados del procesamiento
System.out.println("=== Resumen de procesamiento ===");
System.out.println("Líneas procesadas: " + lineasProcesadas);
System.out.println("Números válidos encontrados: " + numerosValidos.size());
System.out.println("Errores encontrados: " + errores.size());
if (!numerosValidos.isEmpty()) {
int suma = 0;
for (int num : numerosValidos) {
suma += num;
}
double promedio = (double) suma / numerosValidos.size();
System.out.println("\nEstadísticas de números válidos:");
System.out.println("Suma: " + suma);
System.out.printf("Promedio: %.2f%n", promedio);
}
if (!errores.isEmpty()) {
System.out.println("\nDetalle de errores:");
for (String error : errores) {
System.out.println("- " + error);
}
}
}
}
Buenas prácticas y patrones de uso
La implementación efectiva de la clase Scanner
va más allá de su funcionalidad básica. Para desarrollar aplicaciones Java robustas y mantenibles, es fundamental seguir un conjunto de buenas prácticas que optimicen el rendimiento, mejoren la legibilidad del código y eviten problemas comunes.
Gestión adecuada de recursos
Uno de los aspectos más importantes al trabajar con Scanner
es la correcta gestión de sus recursos. El patrón try-with-resources resulta ideal para este propósito:
try (Scanner scanner = new Scanner(new File("datos.txt"))) {
// Operaciones con el scanner
while (scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
System.err.println("No se pudo encontrar el archivo: " + e.getMessage());
}
Este enfoque garantiza que los recursos se liberen adecuadamente incluso si ocurre una excepción durante la ejecución. Para casos donde se necesita mantener el Scanner
durante toda la vida de la aplicación, se recomienda:
public class GestorEntrada {
private static final Scanner ENTRADA = new Scanner(System.in);
// Métodos de utilidad para leer datos
public static void cerrarRecursos() {
if (ENTRADA != null) {
ENTRADA.close();
}
}
}
Separación de responsabilidades
Es recomendable encapsular la lógica de lectura en clases o métodos específicos, siguiendo el principio de responsabilidad única:
public class LectorDatos {
private final Scanner scanner;
public LectorDatos(InputStream fuente) {
this.scanner = new Scanner(fuente);
}
public String leerLinea(String mensaje) {
System.out.print(mensaje);
return scanner.nextLine();
}
public int leerEntero(String mensaje) {
while (true) {
try {
System.out.print(mensaje);
int valor = scanner.nextInt();
scanner.nextLine(); // Limpiar buffer
return valor;
} catch (InputMismatchException e) {
System.out.println("Error: Introduce un número entero válido.");
scanner.nextLine(); // Limpiar entrada inválida
}
}
}
public void cerrar() {
scanner.close();
}
}
Este enfoque facilita la reutilización del código y simplifica las pruebas unitarias.
Patrón de validación progresiva
Para mejorar la experiencia del usuario, se recomienda implementar un patrón de validación progresiva que proporcione retroalimentación inmediata:
public String leerEmail(String mensaje) {
String email;
Pattern patronEmail = Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
while (true) {
System.out.print(mensaje);
email = scanner.nextLine().trim();
if (email.isEmpty()) {
System.out.println("Error: El email no puede estar vacío.");
} else if (!patronEmail.matcher(email).matches()) {
System.out.println("Error: Formato de email inválido.");
} else {
return email;
}
}
}
Uso de delimitadores específicos
Para el procesamiento de formatos estructurados, es más eficiente configurar delimitadores específicos que realizar múltiples operaciones de análisis:
public List<Producto> cargarProductosCSV(String rutaArchivo) throws FileNotFoundException {
List<Producto> productos = new ArrayList<>();
try (Scanner scanner = new Scanner(new File(rutaArchivo))) {
// Configurar delimitador para CSV
scanner.useDelimiter(",|\r?\n");
// Saltar encabezados
if (scanner.hasNextLine()) {
scanner.nextLine();
}
while (scanner.hasNext()) {
String codigo = scanner.next();
String nombre = scanner.next();
double precio = scanner.nextDouble();
int stock = scanner.nextInt();
productos.add(new Producto(codigo, nombre, precio, stock));
}
}
return productos;
}
Implementación del patrón Builder para configuración
Para casos donde se requiere una configuración compleja del Scanner
, el patrón Builder proporciona una interfaz fluida y legible:
public class ScannerBuilder {
private Scanner scanner;
private ScannerBuilder(InputStream source) {
this.scanner = new Scanner(source);
}
public static ScannerBuilder fromInputStream(InputStream source) {
return new ScannerBuilder(source);
}
public static ScannerBuilder fromFile(String path) throws FileNotFoundException {
return new ScannerBuilder(new FileInputStream(path));
}
public static ScannerBuilder fromString(String text) {
return new ScannerBuilder(new ByteArrayInputStream(text.getBytes()));
}
public ScannerBuilder withDelimiter(String pattern) {
scanner.useDelimiter(pattern);
return this;
}
public ScannerBuilder withLocale(Locale locale) {
scanner.useLocale(locale);
return this;
}
public Scanner build() {
return scanner;
}
}
Uso del builder:
Scanner csvScanner = ScannerBuilder.fromFile("datos.csv")
.withDelimiter(",")
.withLocale(Locale.US)
.build();
Estrategia de buffer único para System.in
Cuando se trabaja con la entrada estándar, es recomendable utilizar un único Scanner
para evitar problemas con el cierre de System.in
:
public class EntradaEstandar {
private static final Scanner SCANNER = new Scanner(System.in);
public static String leerLinea() {
return SCANNER.nextLine();
}
public static int leerEntero() {
while (true) {
try {
int valor = SCANNER.nextInt();
SCANNER.nextLine(); // Limpiar buffer
return valor;
} catch (InputMismatchException e) {
System.out.println("Por favor, introduce un número entero válido.");
SCANNER.nextLine(); // Limpiar entrada inválida
}
}
}
// No cerrar el scanner en métodos individuales
// Método para cerrar al finalizar la aplicación
public static void cerrar() {
SCANNER.close();
}
}
Procesamiento por lotes con control de errores
Para el procesamiento de grandes volúmenes de datos, es recomendable implementar un sistema de control de errores que permita continuar con el procesamiento a pesar de encontrar entradas inválidas:
public class ProcesadorLotes {
public static ProcessResult procesarArchivo(String rutaArchivo) {
ProcessResult resultado = new ProcessResult();
int lineaActual = 0;
try (Scanner scanner = new Scanner(new File(rutaArchivo))) {
while (scanner.hasNextLine()) {
lineaActual++;
String linea = scanner.nextLine().trim();
if (linea.isEmpty() || linea.startsWith("#")) {
continue; // Ignorar líneas vacías y comentarios
}
try {
Registro registro = parsearLinea(linea);
resultado.addRegistroValido(registro);
} catch (Exception e) {
resultado.addError(new ErrorProcesamiento(lineaActual, linea, e.getMessage()));
}
}
} catch (FileNotFoundException e) {
resultado.setErrorCritico("Archivo no encontrado: " + e.getMessage());
}
return resultado;
}
private static Registro parsearLinea(String linea) {
// Implementación del parseo
// ...
}
}
Uso de Scanner con expresiones regulares
Para extraer información específica de textos complejos, la combinación de Scanner
con expresiones regulares resulta muy efectiva:
public Map<String, String> extraerDatosEstructurados(String texto) {
Map<String, String> datos = new HashMap<>();
Scanner scanner = new Scanner(texto);
// Buscar patrones específicos
String patronNombre = "Nombre:\\s*([^\\n]+)";
String patronEmail = "Email:\\s*([\\w._%+-]+@[\\w.-]+\\.[a-zA-Z]{2,})";
String patronTelefono = "Tel[ée]fono:\\s*(\\+?\\d[\\d\\s-]{8,})";
if (scanner.findWithinHorizon(patronNombre, 0) != null) {
datos.put("nombre", scanner.match().group(1).trim());
}
if (scanner.findWithinHorizon(patronEmail, 0) != null) {
datos.put("email", scanner.match().group(1).trim());
}
if (scanner.findWithinHorizon(patronTelefono, 0) != null) {
datos.put("telefono", scanner.match().group(1).trim());
}
scanner.close();
return datos;
}
Implementación de caché para operaciones repetitivas
Cuando se necesita procesar repetidamente la misma fuente de datos, es más eficiente implementar un sistema de caché:
public class LectorConfiguracion {
private final String rutaArchivo;
private Map<String, String> propiedades;
public LectorConfiguracion(String rutaArchivo) {
this.rutaArchivo = rutaArchivo;
this.propiedades = null; // Se cargará bajo demanda
}
public String getPropiedad(String clave) {
if (propiedades == null) {
cargarPropiedades();
}
return propiedades.getOrDefault(clave, null);
}
private void cargarPropiedades() {
propiedades = new HashMap<>();
try (Scanner scanner = new Scanner(new File(rutaArchivo))) {
while (scanner.hasNextLine()) {
String linea = scanner.nextLine().trim();
if (linea.isEmpty() || linea.startsWith("#")) {
continue;
}
int separador = linea.indexOf('=');
if (separador > 0) {
String clave = linea.substring(0, separador).trim();
String valor = linea.substring(separador + 1).trim();
propiedades.put(clave, valor);
}
}
} catch (FileNotFoundException e) {
System.err.println("Archivo de configuración no encontrado: " + e.getMessage());
}
}
}
Uso de Scanner en aplicaciones multihilo
En entornos concurrentes, es importante asegurar que el acceso a los objetos Scanner
sea thread-safe:
public class LectorConcurrente {
private final Scanner scanner;
private final Object lock = new Object();
public LectorConcurrente(InputStream fuente) {
this.scanner = new Scanner(fuente);
}
public String leerLinea() {
synchronized (lock) {
if (scanner.hasNextLine()) {
return scanner.nextLine();
}
return null;
}
}
public void cerrar() {
synchronized (lock) {
scanner.close();
}
}
}
Integración con el patrón Observer
Para aplicaciones que requieren notificación de eventos durante la lectura, se puede integrar Scanner
con el patrón Observer:
public class LectorObservable {
private final Scanner scanner;
private final List<LecturaListener> listeners = new ArrayList<>();
public LectorObservable(InputStream fuente) {
this.scanner = new Scanner(fuente);
}
public void agregarListener(LecturaListener listener) {
listeners.add(listener);
}
public void iniciarLectura() {
int numeroLinea = 0;
while (scanner.hasNextLine()) {
numeroLinea++;
String linea = scanner.nextLine();
for (LecturaListener listener : listeners) {
listener.onLineaLeida(numeroLinea, linea);
}
}
for (LecturaListener listener : listeners) {
listener.onFinLectura(numeroLinea);
}
scanner.close();
}
public interface LecturaListener {
void onLineaLeida(int numeroLinea, String contenido);
void onFinLectura(int totalLineas);
}
}
Optimización de rendimiento
Para mejorar el rendimiento al procesar grandes volúmenes de datos, se pueden aplicar varias técnicas:
public List<Dato> procesarArchivoGrande(String rutaArchivo) throws FileNotFoundException {
List<Dato> resultados = new ArrayList<>(10000); // Preasignar capacidad
// Usar BufferedInputStream para mejorar rendimiento
try (Scanner scanner = new Scanner(
new BufferedInputStream(
new FileInputStream(rutaArchivo), 8192),
StandardCharsets.UTF_8.name())) {
// Configurar para procesamiento eficiente
scanner.useDelimiter("\n");
while (scanner.hasNext()) {
String linea = scanner.next();
// Procesar la línea y añadir a resultados
resultados.add(parsearLinea(linea));
}
}
return resultados;
}
Implementación de un sistema de logging para depuración
Para facilitar la depuración, es útil implementar un sistema de logging que registre las operaciones realizadas con Scanner
:
public class ScannerConLog {
private final Scanner scanner;
private final Logger logger;
public ScannerConLog(InputStream fuente, Logger logger) {
this.scanner = new Scanner(fuente);
this.logger = logger;
}
public String nextLine() {
try {
String linea = scanner.nextLine();
logger.fine("Leída línea: " + linea);
return linea;
} catch (NoSuchElementException e) {
logger.warning("Intento de leer línea cuando no hay más disponibles");
throw e;
}
}
public int nextInt() {
try {
int valor = scanner.nextInt();
logger.fine("Leído entero: " + valor);
return valor;
} catch (InputMismatchException e) {
String tokenInvalido = scanner.next();
logger.warning("Token inválido para entero: " + tokenInvalido);
throw new InputMismatchException("No se pudo convertir a entero: " + tokenInvalido);
}
}
// Implementar métodos similares para otros tipos
}
Uso de Scanner en aplicaciones interactivas con menús
Para aplicaciones con interfaces de usuario basadas en texto, se recomienda implementar un sistema de menús que utilice Scanner
de manera eficiente:
public class MenuInteractivo {
private final Scanner scanner;
private final Map<Integer, MenuOpcion> opciones;
public MenuInteractivo(Scanner scanner) {
this.scanner = scanner;
this.opciones = new HashMap<>();
}
public void agregarOpcion(int numero, String descripcion, Runnable accion) {
opciones.put(numero, new MenuOpcion(descripcion, accion));
}
public void mostrarMenu() {
System.out.println("\n=== MENÚ PRINCIPAL ===");
for (Map.Entry<Integer, MenuOpcion> entry : opciones.entrySet()) {
System.out.printf("%d. %s%n", entry.getKey(), entry.getValue().getDescripcion());
}
System.out.print("\nSelecciona una opción: ");
}
public void procesarEntrada() {
try {
int seleccion = scanner.nextInt();
scanner.nextLine(); // Limpiar buffer
MenuOpcion opcion = opciones.get(seleccion);
if (opcion != null) {
opcion.ejecutar();
} else {
System.out.println("Opción no válida. Intenta de nuevo.");
}
} catch (InputMismatchException e) {
System.out.println("Por favor, introduce un número válido.");
scanner.nextLine(); // Limpiar entrada inválida
}
}
private static class MenuOpcion {
private final String descripcion;
private final Runnable accion;
public MenuOpcion(String descripcion, Runnable accion) {
this.descripcion = descripcion;
this.accion = accion;
}
public String getDescripcion() {
return descripcion;
}
public void ejecutar() {
accion.run();
}
}
}
Otros ejercicios de programación de Java
Evalúa tus conocimientos de esta lección La clase Scanner con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Streams: match
Gestión de errores y excepciones
CRUD en Java de modelo Customer sobre un ArrayList
Clases abstractas
Listas
Métodos de la clase String
Streams: reduce()
API java.nio 2
Polimorfismo
Pattern Matching
Streams: flatMap()
Llamada y sobrecarga de funciones
Métodos referenciados
Métodos de la clase String
Representación de Fecha
Operadores lógicos
Inferencia de tipos con var
Tipos de datos
Estructuras de iteración
Streams: forEach()
Objetos
Funciones lambda
Uso de Scanner
Tipos de variables
Streams: collect()
Operadores aritméticos
Arrays y matrices
Clases y objetos
Interfaz funcional Consumer
CRUD en Java de modelo Customer sobre un HashMap
Interfaces
Enumeraciones Enums
API Optional
Interfaz funcional Function
Encapsulación
Interfaces
Uso de API Optional
Representación de Hora
Herencia básica
Clases y objetos
Interfaz funcional Supplier
HashMap
Sobrecarga de métodos
Polimorfismo de tiempo de ejecución
OOP en Java
Sobrecarga de métodos
CRUD de productos en Java
Clases sealed
Creación de Streams
Records
Encapsulación
Streams: min max
Herencia
Métodos avanzados de la clase String
Funciones
Polimorfismo de tiempo de compilación
Reto sintaxis Java
Conjuntos
Estructuras de control
Recursión
Excepciones
Herencia avanzada
Estructuras de selección
Uso de interfaces
Operadores
Variables
HashSet
Objeto Scanner
Streams: filter()
Operaciones de Streams
Interfaz funcional Predicate
Streams: sorted()
Configuración de entorno
Uso de variables
Clases
Streams: distinct()
Streams: count()
ArrayList
Mapas
Datos de referencia
Interfaces funcionales
Métodos básicos de la clase String
Tipos de datos
Clases abstractas
Instalación
Funciones
Excepciones
Estructuras de control
Herencia de clases
La clase Scanner
Generics
Streams: map()
Funciones y encapsulamiento
Todas las lecciones de Java
Accede a todas las lecciones de Java y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Instalación De Java
Introducción Y Entorno
Configuración De Entorno Java
Introducción Y Entorno
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Recursión
Sintaxis
Arrays Y Matrices
Sintaxis
Excepciones
Programación Orientada A Objetos
Clases Y Objetos
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Clases Abstractas
Programación Orientada A Objetos
Interfaces
Programación Orientada A Objetos
Sobrecarga De Métodos
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
La Clase Scanner
Programación Orientada A Objetos
Métodos De La Clase String
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Records
Programación Orientada A Objetos
Pattern Matching
Programación Orientada A Objetos
Inferencia De Tipos Con Var
Programación Orientada A Objetos
Enumeraciones Enums
Programación Orientada A Objetos
Generics
Programación Orientada A Objetos
Clases Sealed
Programación Orientada A Objetos
Listas
Framework Collections
Conjuntos
Framework Collections
Mapas
Framework Collections
Funciones Lambda
Programación Funcional
Interfaz Funcional Consumer
Programación Funcional
Interfaz Funcional Predicate
Programación Funcional
Interfaz Funcional Supplier
Programación Funcional
Interfaz Funcional Function
Programación Funcional
Métodos Referenciados
Programación Funcional
Creación De Streams
Programación Funcional
Operaciones Intermedias Con Streams: Map()
Programación Funcional
Operaciones Intermedias Con Streams: Filter()
Programación Funcional
Operaciones Intermedias Con Streams: Distinct()
Programación Funcional
Operaciones Finales Con Streams: Collect()
Programación Funcional
Operaciones Finales Con Streams: Min Max
Programación Funcional
Operaciones Intermedias Con Streams: Flatmap()
Programación Funcional
Operaciones Intermedias Con Streams: Sorted()
Programación Funcional
Operaciones Finales Con Streams: Reduce()
Programación Funcional
Operaciones Finales Con Streams: Foreach()
Programación Funcional
Operaciones Finales Con Streams: Count()
Programación Funcional
Operaciones Finales Con Streams: Match
Programación Funcional
Api Optional
Programación Funcional
Transformación
Programación Funcional
Reducción Y Acumulación
Programación Funcional
Mapeo
Programación Funcional
Streams Paralelos
Programación Funcional
Agrupación Y Partición
Programación Funcional
Filtrado Y Búsqueda
Programación Funcional
Api Java.nio 2
Entrada Y Salida Io
Fundamentos De Io
Entrada Y Salida Io
Leer Y Escribir Archivos
Entrada Y Salida Io
Httpclient Moderno
Entrada Y Salida Io
Clases De Nio2
Entrada Y Salida Io
Api Java.time
Api Java.time
Localtime
Api Java.time
Localdatetime
Api Java.time
Localdate
Api Java.time
Executorservice
Concurrencia
Virtual Threads (Project Loom)
Concurrencia
Future Y Completablefuture
Concurrencia
Spring Framework
Frameworks Para Java
Micronaut
Frameworks Para Java
Maven
Frameworks Para Java
Gradle
Frameworks Para Java
Lombok Para Java
Frameworks Para Java
Quarkus
Frameworks Para Java
Ecosistema Jakarta Ee De Java
Frameworks Para Java
Introducción A Junit 5
Testing
Certificados de superación de Java
Supera todos los ejercicios de programación del curso de Java y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.
En esta lección
Objetivos de aprendizaje de esta lección
- Importar y utilizar la clase Scanner en Java
- Entender los diferentes constructores de Scanner para varias fuentes de datos
- Personalizar delimitadores y configuración regional
- Leer y convertir datos a diferentes tipos
- Gestionar el ciclo de vida de los objetos Scanner
- Implementar validación de entradas con Scanner