Módulo 04: Entrada y salida por consola

Objetivos del módulo

  • Entender los flujos estándar de un programa: System.in, System.out y System.err
  • Mostrar información con print y println y usar las secuencias de escape
  • Dar formato a la salida con printf, String.format y formatted: decimales, anchos, alineación y separador de miles
  • Entender cómo influye la configuración regional (Locale) en la coma decimal
  • Leer datos del teclado con Scanner: nextInt, nextDouble, next, nextLine…
  • Resolver el famoso problema del salto de línea pendiente tras nextInt
  • Validar la entrada con hasNextInt y conocer la excepción InputMismatchException
  • Usar la clase IO de Java 25 como alternativa moderna, y conocer BufferedReader
  • Recibir argumentos de línea de comandos con args
  • Escribir mensajes de error con System.err y usar redirecciones y tuberías
  • Construir programas interactivos completos: calculadora, conversor de unidades y ticket de compra

1. Entrada y salida estándar

Hasta ahora tus programas han escrito en la consola y, en el módulo 02, han leído números con una “receta” de Scanner. En este módulo vas a entender bien cómo funciona la comunicación entre tu programa y el exterior.

📘 Concepto: Todo programa que se ejecuta en una terminal dispone de tres flujos estándar (standard streams), canales por los que circulan datos: la entrada estándar (por defecto, el teclado), la salida estándar (por defecto, la consola) y la salida de errores (por defecto, también la consola). En Java se accede a ellos con System.in, System.out y System.err.

Los tres flujos estándar: System.in desde el teclado, System.out y System.err hacia la consola, y redirecciones desde la terminal

Figura 1. Los flujos estándar de un programa Java.

Flujo Objeto Java Dirección Por defecto Para qué
Entrada estándar System.in Hacia el programa Teclado Leer datos del usuario
Salida estándar System.out Desde el programa Consola Mostrar resultados
Salida de errores System.err Desde el programa Consola Mostrar avisos y errores

Lo interesante es que la terminal puede redirigir cualquiera de ellos: hacer que el programa lea de un fichero en lugar del teclado, o que escriba los resultados en un fichero. El programa no se entera: sigue leyendo de System.in y escribiendo en System.out. Lo verás en la sección 11.


2. Salida con print y println

System.out es un objeto de tipo PrintStream con dos métodos básicos:

Método Qué hace
System.out.println(x) Escribe x y salta a la línea siguiente
System.out.print(x) Escribe x sin saltar de línea
System.out.println() Solo salta de línea

Ambos aceptan cualquier tipo: textos, números, caracteres, booleanos…

public class Salida {
    public static void main(String[] args) {
        System.out.println("Texto");          // un String
        System.out.println(42);               // un int
        System.out.println(3.5);              // un double
        System.out.println('J');              // un char
        System.out.println(true);             // un boolean
        System.out.println();                 // solo salto de línea
        System.out.print("Sin salto... ");
        System.out.print("seguimos en la misma línea");
        System.out.println();
        int unidades = 3;
        double precio = 2.5;
        System.out.println("Total: " + unidades * precio + " euros");
    }
}
Texto
42
3.5
J
true

Sin salto... seguimos en la misma línea
Total: 7.5 euros

💡 Consejo: Usa print (sin ln) para los mensajes que piden un dato (System.out.print("Edad: ");): así el cursor se queda al final de la línea y el usuario escribe justo detrás de la pregunta.

Recuerda lo que viste en el módulo 03 sobre la concatenación: en "Total: " + unidades * precio + " euros", la multiplicación tiene más precedencia que +, así que se calcula primero (7.5) y después se concatena. Pero con sumas necesitas paréntesis: "Total: " + (a + b).


3. Secuencias de escape

Dentro de un texto entre comillas, la barra invertida \ tiene un significado especial: junto con el carácter siguiente forma una secuencia de escape que representa un carácter que no se puede escribir directamente.

Secuencia Significado
\n Salto de línea (newline)
\t Tabulador
\" Comillas dobles (sin cerrar el texto)
\' Comilla simple (necesaria dentro de un char: '\'')
\\ Barra invertida
\r Retorno de carro (vuelve al principio de la línea)
\b Retroceso (backspace)
\s Espacio (Java 15+, útil en text blocks)
\uXXXX Carácter Unicode por su código hexadecimal
public class Escapes {
    public static void main(String[] args) {
        System.out.println("Línea 1\nLínea 2");             // \n salto de línea
        System.out.println("Nombre\tEdad\tCiudad");        // \t tabulador
        System.out.println("Ana\t20\tValencia");
        System.out.println("Ella dijo: \"Hola\"");          // \" comillas dobles
        System.out.println("Ruta: C:\\java\\hola");          // \\ barra invertida
        System.out.println('\'' + " es una comilla simple"); // \' comilla simple
        System.out.println("Caf\u00E9 y \u00F1and\u00FA");    // códigos Unicode
    }
}
Línea 1
Línea 2
Nombre	Edad	Ciudad
Ana	20	Valencia
Ella dijo: "Hola"
Ruta: C:\java\hola
' es una comilla simple
Café y ñandú

⚠️ Importante: Las secuencias \u se procesan antes que el resto del código, incluso dentro de los comentarios. Si escribes en un comentario algo como \u seguido de letras que no son un código hexadecimal válido, el programa no compila (error: illegal unicode escape). Nos pasó al preparar este mismo ejemplo.

💡 Consejo: Los tabuladores (\t) sirven para alinear columnas rápidamente, pero el resultado depende del ancho del tabulador de cada consola y se descuadra si un texto es largo. Para tablas bien alineadas, usa printf con anchos (sección 4.3).


4. Salida con formato: printf

println muestra los números “tal cual”: 1234.5678, 7.199999999999999. Para mostrar 2 decimales, alinear columnas o poner separador de miles, se usa printf (print formatted).

System.out.printf(formato, valor1, valor2, ...);

El formato es un texto con especificadores (que empiezan por %) donde se insertan los valores, en orden.

System.out.printf("Hola, %s. Tienes %d años.%n", "Ana", 20);
Hola, Ana. Tienes 20 años.

%s se sustituye por el primer valor ("Ana") y %d por el segundo (20). %n es un salto de línea.

⚠️ Importante: printf no salta de línea al final (es como print). Termina el formato con %n, que produce el salto de línea correcto de cada sistema operativo. También funciona \n, pero %n es la opción recomendada.

4.1. Especificadores de conversión

Especificador Tipo de dato Ejemplo Resultado (configuración española)
%d Entero (int, long…) printf("%d", 42) 42
%f Real (double, float) printf("%f", 3.5) 3,500000
%.2f Real con 2 decimales (redondea) printf("%.2f", 1234.5678) 1234,57
%e Real en notación científica printf("%e", 1234.5678) 1,234568e+03
%s Texto (o cualquier valor convertido a texto) printf("%s", "Ana") Ana
%c Carácter printf("%c", 'A') A
%b Booleano printf("%b", true) true
%x / %o Entero en hexadecimal / octal printf("%x", 255) ff
%n Salto de línea    
%% El carácter % printf("15%%") 15%
public class Printf {
    public static void main(String[] args) {
        int edad = 20;
        double precio = 1234.5678;
        String nombre = "Ana";

        System.out.printf("Hola, %s. Tienes %d años.%n", nombre, edad);
        System.out.printf("Precio: %f%n", precio);          // 6 decimales por defecto
        System.out.printf("Precio: %.2f%n", precio);        // 2 decimales (redondea)
        System.out.printf("Precio: %,.2f €%n", precio);     // separador de miles
        System.out.printf("Inicial: %c, ¿mayor? %b%n", nombre.charAt(0), edad >= 18);
        System.out.printf("Científica: %e%n", precio);
        System.out.printf("Hexadecimal: %x, octal: %o%n", 255, 255);
        System.out.printf("Un 15%% de descuento%n");        // %% muestra un %
    }
}

Salida en un ordenador configurado en español:

Hola, Ana. Tienes 20 años.
Precio: 1234,567800
Precio: 1234,57
Precio: 1.234,57 €
Inicial: A, ¿mayor? true
Científica: 1,234568e+03
Hexadecimal: ff, octal: 377
Un 15% de descuento

Fíjate en que printf usa la coma decimal y el punto para los miles, como se escribe en España. Lo explicamos en la sección 5.

⚠️ Importante: El tipo del valor debe encajar con el especificador. printf("%d", 3.5) provoca una excepción IllegalFormatConversionException (d != java.lang.Double), y printf("%.2f", 5) también falla, porque 5 es un int: escribe 5.0 o haz un casting. Y si pones más especificadores que valores, obtendrás MissingFormatArgumentException.

4.2. Anatomía de un especificador

Un especificador completo tiene esta forma: %[opciones][ancho][.precisión]conversión.

Anatomía del especificador %-10.2f: inicio, opciones, ancho, precisión y conversión

Figura 2. Las partes de un especificador de formato.

Parte Qué indica Ejemplos
Opciones (flags) - alinear a la izquierda; 0 rellenar con ceros; + mostrar siempre el signo; , separador de miles; ( negativos entre paréntesis %-10s, %05d, %+d, %,d
Ancho Número mínimo de caracteres que ocupará el valor. Si el valor es más corto, se rellena con espacios (a la izquierda por defecto, es decir, alineado a la derecha) %8d, %10.2f
Precisión En reales: número de decimales. En textos: número máximo de caracteres %.2f, %.3s
Conversión El tipo de dato (obligatoria) d, f, s, c, b, e, x, n

4.3. Anchos y alineación

public class Anchos {
    public static void main(String[] args) {
        System.out.printf("[%5d]%n", 42);        // ancho 5, a la derecha
        System.out.printf("[%-5d]%n", 42);       // a la izquierda
        System.out.printf("[%05d]%n", 42);       // relleno con ceros
        System.out.printf("[%+d]%n", 42);        // signo siempre
        System.out.printf("[%,d]%n", 1234567);   // miles
        System.out.printf("[%10.2f]%n", 3.14159);
        System.out.printf("[%-10.2f]%n", 3.14159);
        System.out.printf("[%10s]%n", "Java");
        System.out.printf("[%-10s]%n", "Java");
        System.out.printf("[%.3s]%n", "Programación");   // solo 3 caracteres
        System.out.printf("%2$s %1$s%n", "mundo", "Hola");  // índice de argumento
    }
}
[   42]
[42   ]
[00042]
[+42]
[1.234.567]
[      3,14]
[3,14      ]
[      Java]
[Java      ]
[Pro]
Hola mundo

Los corchetes del ejemplo sirven para ver los espacios de relleno. El último caso muestra que con 2$ y 1$ puedes indicar qué argumento usar, en lugar de seguir el orden.

Con anchos fijos es fácil construir tablas alineadas. La regla habitual: textos a la izquierda (%-12s) y números a la derecha (%8d, %10.2f), para que las unidades, decenas y decimales queden en columna:

public class Tabla {
    public static void main(String[] args) {
        System.out.printf("%-12s %8s %10s%n", "Producto", "Unidades", "Precio");
        System.out.printf("%-12s %8d %10.2f%n", "Teclado", 2, 24.9);
        System.out.printf("%-12s %8d %10.2f%n", "Ratón", 10, 9.5);
        System.out.printf("%-12s %8d %10.2f%n", "Monitor 27\"", 1, 189.99);
    }
}
Producto     Unidades     Precio
Teclado             2      24,90
Ratón              10       9,50
Monitor 27"         1     189,99

4.4. String.format y formatted

A veces no quieres mostrar el texto formateado, sino guardarlo en una variable para usarlo después. Para eso están:

  • String.format(formato, valores...): igual que printf, pero devuelve un String.
  • "formato".formatted(valores...) (Java 15+): lo mismo, llamado sobre el propio texto del formato.
public class Formato {
    public static void main(String[] args) {
        double nota = 7.456;
        String a = String.format("Nota: %.1f", nota);     // devuelve un String
        String b = "Nota: %.2f".formatted(nota);          // Java 15+
        System.out.println(a);
        System.out.println(b);
        System.out.println(a.length() + " caracteres");
    }
}
Nota: 7,5
Nota: 7,46
9 caracteres

💡 Consejo: printf, String.format y formatted usan exactamente los mismos especificadores. Aprende uno y sabrás los tres. Y recuerda: formatear solo cambia cómo se muestra el número; la variable nota sigue valiendo 7.456.


5. Configuración regional: la coma decimal

¿Por qué printf escribe 3,14 y println escribe 3.14? Porque printf, String.format y Scanner tienen en cuenta la configuración regional (locale) del sistema, mientras que println y la concatenación de textos siempre usan el punto.

📘 Concepto: Un Locale representa una configuración regional: idioma y país (es_ES es español de España; en_US, inglés de Estados Unidos). Determina el separador decimal, el de miles, el formato de las fechas, etc. Java toma por defecto la del sistema operativo: si tu Windows está en español de España, será es_ES.

import java.util.Locale;

public class Locales {
    public static void main(String[] args) {
        double importe = 1234567.891;
        System.out.println(Locale.getDefault());
        System.out.printf("%,.2f%n", importe);                       // configuración regional por defecto
        System.out.printf(Locale.US, "%,.2f%n", importe);            // Estados Unidos
        System.out.printf(Locale.of("es", "ES"), "%,.2f%n", importe);  // España
        System.out.printf(Locale.GERMANY, "%,.2f%n", importe);       // Alemania
        System.out.println(importe);                                 // println no usa la configuración regional
        String texto = String.format(Locale.ROOT, "%.2f", 3.5);      // siempre con punto
        System.out.println(texto);
    }
}

En un ordenador configurado en español:

es_ES
1.234.567,89
1,234,567.89
1.234.567,89
1.234.567,89
1234567.891
3.50

Y el mismo programa en un ordenador configurado en inglés de Estados Unidos (lo simulamos con java -Duser.language=en -Duser.country=US Locales.java):

en_US
1,234,567.89
1,234,567.89
1.234.567,89
1.234.567,89
1234567.891
3.50

Solo cambian las líneas que dependen de la configuración por defecto. Si pasas un Locale como primer argumento de printf o String.format, el resultado es el mismo en cualquier ordenador.

Constante o método Configuración
Locale.getDefault() La del sistema
Locale.of("es", "ES") Español de España (Locale.of existe desde Java 19)
Locale.US Inglés de Estados Unidos (punto decimal, coma para miles)
Locale.UK, Locale.FRANCE, Locale.GERMANY… Otros países
Locale.ROOT Neutra: punto decimal, sin depender del país

💡 Consejo: Los ejemplos de este curso se han ejecutado con la configuración española (es_ES), que es la que tendrá un ordenador con Windows en español. Si tu sistema está en otro idioma y ves puntos donde aquí hay comas (o al revés), ya sabes por qué.


6. Entrada con Scanner

La clase java.util.Scanner es la forma clásica de leer datos del teclado en Java. “Escanea” el texto que escribe el usuario y lo convierte en el tipo de dato que le pidas.

6.1. Crear el Scanner

import java.util.Scanner;                     // 1. importar la clase (antes de "public class")

public class Ejemplo {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);   // 2. crear UN Scanner sobre la entrada estándar
        int n = teclado.nextInt();                  // 3. leer
    }
}
  1. import java.util.Scanner;: Scanner está en el paquete java.util, que no se importa automáticamente (a diferencia de java.lang, donde están System, String o Math). Sin el import, obtendrías cannot find symbol.
  2. new Scanner(System.in) crea un objeto Scanner que lee de la entrada estándar. Le damos el nombre teclado (otros nombres habituales: sc, entrada, in).
  3. Cada llamada a un método next... espera a que el usuario escriba algo y pulse Intro, y devuelve el siguiente dato.

⚠️ Importante: Crea un solo Scanner sobre System.in en todo el programa y reutilízalo. Y no lo cierres con teclado.close() si vas a seguir leyendo: cerrar el Scanner cierra también System.in, y ya no podrías volver a leer del teclado en ese programa.

6.2. Métodos de lectura

Método Lee Ejemplo de entrada Devuelve
nextInt() Un entero 42 int 42
nextLong() Un entero largo 9000000000 long
nextDouble() Un número real 1,75 (configuración española) double 1.75
nextBoolean() Un booleano true boolean
next() Una palabra (hasta el siguiente espacio o salto de línea) Ana María "Ana"
nextLine() El resto de la línea, con espacios, hasta el salto de línea Ana María "Ana María"

Scanner no tiene nextChar(). Para leer un carácter: char c = teclado.next().charAt(0); (lee una palabra y se queda con su primer carácter).

import java.util.Scanner;

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

        System.out.print("Edad: ");
        int edad = teclado.nextInt();
        System.out.print("Altura en metros: ");
        double altura = teclado.nextDouble();
        System.out.print("¿Tienes carnet? (true/false): ");
        boolean carnet = teclado.nextBoolean();
        System.out.print("Nombre (una palabra): ");
        String nombre = teclado.next();

        System.out.println(nombre + ", " + edad + " años, " + altura + " m, carnet: " + carnet);
    }
}

Ejecución (lo que escribe el usuario aparece detrás de cada pregunta):

Edad: 20
Altura en metros: 1,75
¿Tienes carnet? (true/false): true
Nombre (una palabra): Ana
Ana, 20 años, 1.75 m, carnet: true

Observa que la altura se escribe con coma (1,75, porque Scanner usa la configuración española) y se muestra con punto (1.75, porque se muestra con concatenación, no con printf).

6.3. El separador decimal al leer

Si en un ordenador configurado en español el usuario escribe 19.99 donde se espera un double, el programa falla:

import java.util.Scanner;

public class ErrorDecimal {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);
        System.out.print("Precio: ");
        double precio = teclado.nextDouble();
        System.out.println("Con IVA: " + precio * 1.21);
    }
}
Precio: 19.99
Exception in thread "main" java.util.InputMismatchException
	at java.base/java.util.Scanner.throwFor(Scanner.java:977)
	at java.base/java.util.Scanner.next(Scanner.java:1632)
	at java.base/java.util.Scanner.nextDouble(Scanner.java:2603)
	at ErrorDecimal.main(ErrorDecimal.java:7)

Tienes dos opciones: escribir 19,99, o cambiar la configuración regional del Scanner con useLocale:

import java.util.Locale;
import java.util.Scanner;

public class ScannerUS {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);
        teclado.useLocale(Locale.US);          // ahora acepta el punto decimal
        System.out.print("Precio: ");
        double precio = teclado.nextDouble();
        System.out.printf("Con IVA: %.2f%n", precio * 1.21);
    }
}
Precio: 19.99
Con IVA: 24,19

(El printf sigue mostrando la coma porque usa la configuración del sistema; el useLocale solo afecta a la lectura de ese Scanner.)

6.4. next() frente a nextLine()

import java.util.Scanner;

public class NextVsNextLine {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);
        System.out.print("Escribe tu nombre completo: ");
        String palabra = teclado.next();       // solo hasta el primer espacio
        String resto = teclado.nextLine();     // el resto de la línea
        System.out.println("next():     [" + palabra + "]");
        System.out.println("nextLine(): [" + resto + "]");
    }
}
Escribe tu nombre completo: Ana María López
next():     [Ana]
nextLine(): [ María López]

next() se detiene en el primer espacio; nextLine() recoge todo lo que queda de la línea (incluido el espacio inicial). Para leer nombres, direcciones o frases, usa nextLine().

📘 Concepto: Scanner divide la entrada en tokens separados por espacios, tabuladores y saltos de línea. nextInt(), nextDouble(), next()… leen un token: por eso puedes escribir varios números en la misma línea (4 9 2) y leerlos con tres nextInt(). nextLine() es distinto: no lee tokens, sino caracteres hasta el salto de línea.


7. El problema del salto de línea pendiente

Este es, con diferencia, el error más famoso al usar Scanner. Mira este programa:

import java.util.Scanner;

public class Problema {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);
        System.out.print("Edad: ");
        int edad = teclado.nextInt();
        System.out.print("Nombre: ");
        String nombre = teclado.nextLine();   // ¡lee el salto de línea pendiente!
        System.out.println("Hola, [" + nombre + "], tienes " + edad + " años");
    }
}

El usuario escribe 25, pulsa Intro… y el programa no le deja escribir el nombre:

Edad: 25
Nombre: Hola, [], tienes 25 años

7.1. ¿Qué ha pasado?

Cuando el usuario escribe 25 y pulsa Intro, al búfer de entrada llegan tres caracteres: 2, 5 y el salto de línea (\n). nextInt() lee el número 25 y se detiene justo antes del salto de línea, sin consumirlo. Después, nextLine() lee “hasta el siguiente salto de línea”… y se lo encuentra inmediatamente: devuelve una cadena vacía sin esperar a que el usuario escriba.

Búfer de entrada con 25, salto de línea, Ana y salto de línea; nextInt deja el salto pendiente y nextLine devuelve una cadena vacía

Figura 3. El salto de línea que deja nextInt() en el búfer lo consume el siguiente nextLine().

El problema aparece siempre que haya un nextLine() después de nextInt(), nextDouble(), nextBoolean() o next(). No aparece si solo usas nextLine(), o si solo usas los métodos de tokens.

7.2. Solución 1: consumir el salto de línea

Añade un nextLine() “de limpieza” justo después del nextInt(), sin guardar su resultado:

import java.util.Scanner;

public class Solucion1 {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);
        System.out.print("Edad: ");
        int edad = teclado.nextInt();
        teclado.nextLine();                   // descarta el salto de línea pendiente
        System.out.print("Nombre: ");
        String nombre = teclado.nextLine();
        System.out.println("Hola, [" + nombre + "], tienes " + edad + " años");
    }
}
Edad: 25
Nombre: Ana López
Hola, [Ana López], tienes 25 años

7.3. Solución 2: leer siempre líneas completas

Lee todo con nextLine() y convierte el texto en número con Integer.parseInt o Double.parseDouble (módulo 03). Así nunca queda nada pendiente:

import java.util.Scanner;

public class Solucion2 {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);
        System.out.print("Edad: ");
        int edad = Integer.parseInt(teclado.nextLine().trim());   // siempre líneas completas
        System.out.print("Nombre: ");
        String nombre = teclado.nextLine();
        System.out.println("Hola, [" + nombre + "], tienes " + edad + " años");
    }
}
Edad: 25
Nombre: Ana López
Hola, [Ana López], tienes 25 años

trim() elimina los espacios que el usuario haya podido escribir antes o después del número.

Solución Ventajas Inconvenientes
nextLine() de limpieza Mínimo cambio en el código Hay que acordarse cada vez
Todo con nextLine() + parseInt Sin sorpresas; también funciona igual con IO.readln Double.parseDouble usa siempre el punto decimal, no la configuración regional
Ordenar las lecturas Leer primero los textos y después los números No siempre es posible

⚠️ Importante: Si usas Double.parseDouble, el usuario debe escribir 1.75 (con punto), mientras que nextDouble() en un sistema en español espera 1,75. Decide una forma y sé coherente, e indícaselo al usuario en el mensaje.


8. Validar la entrada

¿Qué pasa si el programa pide un número y el usuario escribe una palabra?

import java.util.Scanner;

public class ErrorTipo {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);
        System.out.print("Número: ");
        int n = teclado.nextInt();
        System.out.println("El doble es " + n * 2);
    }
}
Número: hola
Exception in thread "main" java.util.InputMismatchException
	at java.base/java.util.Scanner.throwFor(Scanner.java:977)
	at java.base/java.util.Scanner.next(Scanner.java:1632)
	at java.base/java.util.Scanner.nextInt(Scanner.java:2297)
	at java.base/java.util.Scanner.nextInt(Scanner.java:2251)
	at ErrorTipo.main(ErrorTipo.java:7)

El programa termina con una excepción InputMismatchException (“la entrada no coincide con el tipo esperado”). Cómo leer esta traza: la primera línea dice qué ha pasado, y la última línea que menciona tu código (ErrorTipo.java:7) dice dónde. Las líneas intermedias son el recorrido interno por la clase Scanner. En el módulo 15 aprenderás a capturar excepciones con try/catch; de momento, la solución es evitar que se produzcan comprobando antes el dato.

8.1. hasNextInt y compañía

Los métodos hasNext...() miran el siguiente token sin consumirlo y devuelven true si se puede leer con el tipo indicado:

Método Devuelve true si el siguiente token es…
hasNextInt() Un int válido
hasNextDouble() Un double válido (según la configuración regional)
hasNextBoolean() true o false
hasNext() Cualquier token (hay más datos)
hasNextLine() Hay otra línea

El patrón para repetir la pregunta hasta que el dato sea correcto usa un bucle while (lo estudiarás a fondo en el módulo 06):

import java.util.Scanner;

public class Validar {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);
        System.out.print("Introduce tu edad: ");
        while (!teclado.hasNextInt()) {            // ¿lo siguiente es un int?
            String basura = teclado.next();        // descarta lo que no es un número
            System.out.print("'" + basura + "' no es un número entero. Prueba otra vez: ");
        }
        int edad = teclado.nextInt();
        System.out.println("Edad registrada: " + edad);
    }
}
Introduce tu edad: veinte
'veinte' no es un número entero. Prueba otra vez: 20,5
'20,5' no es un número entero. Prueba otra vez: 20
Edad registrada: 20

⚠️ Importante: Dentro del bucle hay que consumir el dato incorrecto con teclado.next(). Si no lo haces, hasNextInt() seguirá viendo el mismo token veinte una y otra vez y el bucle no terminará nunca.

8.2. Validar el tipo y el rango

A menudo no basta con que sea un número: tiene que estar en un rango (una nota entre 0 y 10, una edad entre 0 y 120…). Combinando hasNextInt con una condición:

import java.util.Scanner;

public class ValidarRango {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);
        int nota = -1;
        while (nota < 0 || nota > 10) {
            System.out.print("Nota (0-10): ");
            if (teclado.hasNextInt()) {
                nota = teclado.nextInt();
                if (nota < 0 || nota > 10) {
                    System.out.println("Fuera de rango.");
                }
            } else {
                System.out.println("Eso no es un número.");
                teclado.next();
            }
        }
        System.out.println("Nota válida: " + nota);
    }
}
Nota (0-10): ocho
Eso no es un número.
Nota (0-10): 15
Fuera de rango.
Nota (0-10): 8
Nota válida: 8

💡 Consejo: Un buen programa nunca confía en el usuario. Valida siempre los datos de entrada: el tipo, el rango y los casos especiales (cadenas vacías, negativos…). Es una de las primeras cosas que se evalúan en las prácticas.

8.3. Tildes y eñes en la consola de Windows

Si en la consola de Windows lees con nextLine() un texto con tildes o eñes y aparecen caracteres extraños, el motivo es que la consola y Java no usan la misma codificación de caracteres. Desde Java 25, la documentación de Scanner recomienda indicar la codificación de la entrada estándar, disponible en la propiedad stdin.encoding:

Scanner teclado = new Scanner(System.in, System.getProperty("stdin.encoding"));

En IntelliJ IDEA y en Linux o macOS normalmente no hace falta, porque todo usa UTF-8.


9. La clase IO de Java 25

En el módulo 01 conociste la forma compacta de Java 25. Junto a ella llegó la clase java.lang.IO, con métodos estáticos muy sencillos para la consola. Al estar en java.lang, no necesita import y se puede usar en cualquier programa de Java 25, compacto o clásico.

Método Qué hace
IO.println(x) Escribe x y salta de línea (como System.out.println)
IO.println() Solo salta de línea
IO.print(x) Escribe x sin saltar de línea
IO.readln() Lee una línea completa y la devuelve como String
IO.readln(mensaje) Muestra el mensaje y después lee una línea
public class ConIO {
    public static void main(String[] args) {
        String nombre = IO.readln("Nombre: ");               // muestra el mensaje y lee una línea
        int edad = Integer.parseInt(IO.readln("Edad: "));    // siempre devuelve String: hay que convertir
        double altura = Double.parseDouble(IO.readln("Altura (m): "));
        IO.println("Hola, " + nombre + ".");
        IO.print("Dentro de 10 años tendrás ");
        IO.println(edad + 10);
        IO.println(String.format("Mides %.2f m", altura));
    }
}
Nombre: Ana López
Edad: 20
Altura (m): 1.68
Hola, Ana López.
Dentro de 10 años tendrás 30
Mides 1,68 m

Ventajas de IO frente a Scanner:

  • No hay que importar nada ni crear ningún objeto.
  • El mensaje y la lectura van en una sola instrucción.
  • Como siempre lee líneas completas, no existe el problema del salto de línea pendiente.

Inconvenientes:

  • Siempre devuelve String: hay que convertir los números con Integer.parseInt o Double.parseDouble (que usa punto decimal, como se ve con 1.68).
  • No tiene métodos de validación como hasNextInt.
  • Si la entrada se acaba (por ejemplo, si se redirige desde un fichero con menos líneas de las esperadas), IO.readln devuelve null, y al convertirlo:
Nombre: Ana
Edad: Exception in thread "main" java.lang.NumberFormatException: Cannot parse null string
	at java.base/java.lang.Integer.parseInt(Integer.java:527)
	at java.base/java.lang.Integer.parseInt(Integer.java:662)
	at ConIO.main(ConIO.java:4)
  • Solo existe desde Java 25: el código no compila en versiones anteriores.

💡 Consejo: En este curso seguiremos usando System.out y Scanner, que son lo que encontrarás en exámenes, libros y proyectos existentes. IO es perfecta para pruebas rápidas y para programas compactos con void main().


10. Otras formas de leer: BufferedReader

Antes de que existiera Scanner (Java 5), la forma habitual de leer de teclado era BufferedReader. Todavía la verás en código antiguo y en programación competitiva, porque es más rápida con grandes volúmenes de datos:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class ConBuffered {
    public static void main(String[] args) throws IOException {
        BufferedReader entrada = new BufferedReader(new InputStreamReader(System.in));
        System.out.print("Escribe una frase: ");
        String linea = entrada.readLine();
        System.out.println("Has escrito " + linea.length() + " caracteres");
    }
}
Escribe una frase: Java es divertido
Has escrito 17 caracteres

Solo lee líneas (readLine()), así que los números hay que convertirlos con parseInt. El throws IOException en main es obligatorio porque readLine() puede producir un error de entrada/salida; lo entenderás en el módulo 15. Usarás BufferedReader para leer ficheros en el módulo 17.


11. Argumentos de línea de comandos

¿Recuerdas el parámetro String[] args de main? Es un array con las palabras que se escriben detrás del nombre del programa al ejecutarlo. Son otra forma de pasar datos al programa, sin preguntar nada al usuario.

public class Argumentos {
    public static void main(String[] args) {
        System.out.println("Número de argumentos: " + args.length);
        if (args.length >= 2) {
            String nombre = args[0];
            int edad = Integer.parseInt(args[1]);   // los argumentos siempre son String
            System.out.println("Hola, " + nombre + ". El año que viene tendrás " + (edad + 1));
        } else {
            System.err.println("Uso: java Argumentos <nombre> <edad>");
        }
    }
}

Ejecuciones reales:

$ java Argumentos.java Ana 20
Número de argumentos: 2
Hola, Ana. El año que viene tendrás 21
$ java Argumentos.java "Ana María" 20 extra
Número de argumentos: 3
Hola, Ana María. El año que viene tendrás 21
$ java Argumentos.java
Número de argumentos: 0
Uso: java Argumentos <nombre> <edad>
  • args.length es el número de argumentos; args[0] es el primero, args[1] el segundo… (los arrays empiezan a contar en 0; módulo 08).
  • Los argumentos se separan por espacios; para pasar un texto con espacios, ponlo entre comillas.
  • Siempre son String: para usarlos como números hay que convertirlos.
  • Si el programa necesita argumentos y no los recibe, lo correcto es mostrar un mensaje de uso por System.err.

💡 Consejo: En IntelliJ IDEA, los argumentos se indican en la configuración de ejecución: en el widget de ejecución de la barra superior, despliega el menú y elige Edit Configurations…; escribe los argumentos en el campo Program arguments y pulsa OK.


12. System.err, redirecciones y tuberías

12.1. La salida de errores

System.err funciona igual que System.out (println, print, printf), pero escribe en la salida de errores. En la consola ambas se ven mezcladas (IntelliJ muestra System.err en rojo), pero el sistema operativo las trata como canales distintos, y eso permite separarlas.

public class Errores {
    public static void main(String[] args) {
        System.out.println("Inicio del proceso");
        System.err.println("Aviso: falta el fichero de configuración");
        System.out.println("Fin del proceso");
    }
}
Inicio del proceso
Aviso: falta el fichero de configuración
Fin del proceso

⚠️ Importante: Como son dos canales independientes, el orden en que aparecen mezclados en pantalla no siempre es el de las instrucciones: a veces un mensaje de System.err sale antes o después de lo esperado. No lo uses para la salida normal del programa.

12.2. Redirecciones

Las terminales permiten redirigir los flujos estándar a ficheros sin cambiar el programa:

Redirección Significado cmd / Bash / zsh PowerShell
> fichero La salida estándar va al fichero Sí Sí
2> fichero La salida de errores va al fichero Sí Sí
< fichero La entrada estándar se lee del fichero Sí No (usa Get-Content fichero \| java ...)
programa1 \| programa2 Tubería: la salida de uno es la entrada del otro Sí Sí

Sesión real redirigiendo la salida del programa anterior:

$ java Errores.java > salida.txt
Aviso: falta el fichero de configuración
$ cat salida.txt
Inicio del proceso
Fin del proceso

$ java Errores.java 2> errores.txt
Inicio del proceso
Fin del proceso
$ cat errores.txt
Aviso: falta el fichero de configuración

Con > solo la salida estándar va al fichero y el aviso sigue apareciendo en pantalla; con 2>, ocurre al revés. (cat muestra un fichero en Bash; en Windows usa type en cmd o Get-Content en PowerShell.)

12.3. Pasar la entrada por tubería

Las tuberías y la redirección de entrada son muy útiles para probar programas interactivos sin tener que teclear los datos una y otra vez:

# Bash (Linux, macOS, Git Bash): printf interpreta \n como salto de línea
printf "7,5\n2\n" | java Calculadora.java

# Bash o cmd: leer la entrada de un fichero con un dato por línea
java Calculadora.java < datos.txt
# PowerShell: cada texto de la lista se envía como una línea
"7,5", "2" | java Calculadora.java
:: cmd (símbolo del sistema)
(echo 7,5& echo 2) | java Calculadora.java

Cuando la entrada llega por tubería, lo que “escribe el usuario” no aparece en pantalla (nadie lo está tecleando), así que las preguntas salen seguidas. Los programas completos de la sección siguiente se han ejecutado así, y verás las dos versiones: la salida real de la tubería y cómo se vería en una consola interactiva.


13. Programas interactivos completos

13.1. Calculadora

Lee dos números reales y muestra las operaciones básicas con formato.

import java.util.Scanner;

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

        System.out.println("=== CALCULADORA ===");
        System.out.print("Primer número: ");
        double a = teclado.nextDouble();
        System.out.print("Segundo número: ");
        double b = teclado.nextDouble();

        System.out.printf("%.2f + %.2f = %.2f%n", a, b, a + b);
        System.out.printf("%.2f - %.2f = %.2f%n", a, b, a - b);
        System.out.printf("%.2f x %.2f = %.2f%n", a, b, a * b);
        System.out.printf("%.2f / %.2f = %.4f%n", a, b, a / b);
        System.out.printf("%.2f elevado a %.2f = %.4f%n", a, b, Math.pow(a, b));
        System.out.printf("Raíz cuadrada de %.2f = %.4f%n", a, Math.sqrt(a));
    }
}

Ejecución real con la entrada por tubería:

$ printf "7,5\n2\n" | java Calculadora.java
=== CALCULADORA ===
Primer número: Segundo número: 7,50 + 2,00 = 9,50
7,50 - 2,00 = 5,50
7,50 x 2,00 = 15,00
7,50 / 2,00 = 3,7500
7,50 elevado a 2,00 = 56,2500
Raíz cuadrada de 7,50 = 2,7386

Lo mismo tecleando los datos en la consola:

=== CALCULADORA ===
Primer número: 7,5
Segundo número: 2
7,50 + 2,00 = 9,50
7,50 - 2,00 = 5,50
7,50 x 2,00 = 15,00
7,50 / 2,00 = 3,7500
7,50 elevado a 2,00 = 56,2500
Raíz cuadrada de 7,50 = 2,7386

¿Qué pasaría si el segundo número fuera 0? La división de reales daría Infinity (módulo 03). En el módulo 05 aprenderás a comprobarlo con un if antes de dividir.

13.2. Conversor de unidades

Convierte una distancia, una temperatura y un peso, mostrando los resultados en columnas alineadas. Los factores de conversión son constantes.

import java.util.Scanner;

public class Conversor {
    public static void main(String[] args) {
        final double KM_POR_MILLA = 1.609344;
        final double KG_POR_LIBRA = 0.45359237;
        Scanner teclado = new Scanner(System.in);

        System.out.println("=== CONVERSOR DE UNIDADES ===");
        System.out.print("Distancia en kilómetros: ");
        double km = teclado.nextDouble();
        System.out.print("Temperatura en ºC: ");
        double celsius = teclado.nextDouble();
        System.out.print("Peso en kg: ");
        double kg = teclado.nextDouble();

        System.out.println();
        System.out.printf("%10.2f km  = %10.2f millas%n", km, km / KM_POR_MILLA);
        System.out.printf("%10.1f ºC  = %10.1f ºF%n", celsius, celsius * 9 / 5 + 32);
        System.out.printf("%10.2f kg  = %10.2f libras%n", kg, kg / KG_POR_LIBRA);
    }
}
$ printf "42,195\n36,6\n70\n" | java Conversor.java
=== CONVERSOR DE UNIDADES ===
Distancia en kilómetros: Temperatura en ºC: Peso en kg: 
     42,20 km  =      26,22 millas
      36,6 ºC  =       97,9 ºF
     70,00 kg  =     154,32 libras

En la consola:

=== CONVERSOR DE UNIDADES ===
Distancia en kilómetros: 42,195
Temperatura en ºC: 36,6
Peso en kg: 70

     42,20 km  =      26,22 millas
      36,6 ºC  =       97,9 ºF
     70,00 kg  =     154,32 libras

Fíjate en que %10.2f redondea los 42,195 km de un maratón a 42,20 al mostrarlos, pero el cálculo se hace con el valor completo.

13.3. Ticket de compra formateado

Lee tres productos (nombre, precio y unidades) y muestra un ticket con columnas alineadas, la base imponible, el IVA y el total. Observa cómo se combinan nextLine() para los nombres (que pueden tener espacios) con nextDouble() y nextInt(), y los nextLine() de limpieza.

import java.util.Scanner;

public class Ticket {
    public static void main(String[] args) {
        final double IVA = 0.21;
        Scanner teclado = new Scanner(System.in);

        System.out.print("Producto 1: ");
        String p1 = teclado.nextLine();
        System.out.print("  Precio: ");
        double precio1 = teclado.nextDouble();
        System.out.print("  Unidades: ");
        int uds1 = teclado.nextInt();
        teclado.nextLine();                               // salto de línea pendiente

        System.out.print("Producto 2: ");
        String p2 = teclado.nextLine();
        System.out.print("  Precio: ");
        double precio2 = teclado.nextDouble();
        System.out.print("  Unidades: ");
        int uds2 = teclado.nextInt();
        teclado.nextLine();

        System.out.print("Producto 3: ");
        String p3 = teclado.nextLine();
        System.out.print("  Precio: ");
        double precio3 = teclado.nextDouble();
        System.out.print("  Unidades: ");
        int uds3 = teclado.nextInt();

        double importe1 = precio1 * uds1;
        double importe2 = precio2 * uds2;
        double importe3 = precio3 * uds3;
        double base = importe1 + importe2 + importe3;
        double cuotaIva = base * IVA;
        double total = base + cuotaIva;

        String linea = "-".repeat(44);
        System.out.println();
        System.out.println(linea);
        System.out.printf("%-20s %5s %8s %8s%n", "PRODUCTO", "UDS", "PRECIO", "IMPORTE");
        System.out.println(linea);
        System.out.printf("%-20s %5d %8.2f %8.2f%n", p1, uds1, precio1, importe1);
        System.out.printf("%-20s %5d %8.2f %8.2f%n", p2, uds2, precio2, importe2);
        System.out.printf("%-20s %5d %8.2f %8.2f%n", p3, uds3, precio3, importe3);
        System.out.println(linea);
        System.out.printf("%-35s %8.2f%n", "Base imponible", base);
        System.out.printf("%-35s %8.2f%n", "IVA (21 %)", cuotaIva);
        System.out.printf("%-35s %8.2f%n", "TOTAL (€)", total);
        System.out.println(linea);
    }
}

Esta vez los datos se leen de un fichero ticket.txt con un dato por línea:

Cuaderno A4
2,35
4
Bolígrafo azul
0,90
10
Mochila
24,99
1
$ java Ticket.java < ticket.txt
Producto 1:   Precio:   Unidades: Producto 2:   Precio:   Unidades: Producto 3:   Precio:   Unidades: 
--------------------------------------------
PRODUCTO               UDS   PRECIO  IMPORTE
--------------------------------------------
Cuaderno A4              4     2,35     9,40
Bolígrafo azul          10     0,90     9,00
Mochila                  1    24,99    24,99
--------------------------------------------
Base imponible                         43,39
IVA (21 %)                              9,11
TOTAL (€)                              52,50
--------------------------------------------

En una consola interactiva:

Producto 1: Cuaderno A4
  Precio: 2,35
  Unidades: 4
Producto 2: Bolígrafo azul
  Precio: 0,90
  Unidades: 10
Producto 3: Mochila
  Precio: 24,99
  Unidades: 1

--------------------------------------------
PRODUCTO               UDS   PRECIO  IMPORTE
--------------------------------------------
Cuaderno A4              4     2,35     9,40
Bolígrafo azul          10     0,90     9,00
Mochila                  1    24,99    24,99
--------------------------------------------
Base imponible                         43,39
IVA (21 %)                              9,11
TOTAL (€)                              52,50
--------------------------------------------

Detalles a observar:

  • "-".repeat(44) (Java 11+) repite un texto: crea la línea de 44 guiones sin escribirlos a mano.
  • El ancho de cada columna de datos coincide con el de su cabecera, así que todo queda alineado: %-20s + espacio + %5d + espacio + %8.2f + espacio + %8.2f = 44 caracteres, igual que la línea.
  • En "IVA (21 %)" el % está dentro de un valor, no del formato, así que no hace falta escribir %%.
  • Repetir tres veces el mismo bloque de lectura no es elegante. En los módulos 06 (bucles), 07 (métodos) y 08 (arrays) aprenderás a hacerlo para cualquier número de productos.

14. Errores frecuentes

Error Síntoma Solución
Olvidar import java.util.Scanner; cannot find symbol ... class Scanner Añade el import antes de la clase
nextLine() después de nextInt() Se “salta” la lectura del texto (cadena vacía) nextLine() de limpieza o leer todo con nextLine()
Escribir 19.99 con configuración española InputMismatchException en nextDouble() Escribe 19,99 o usa useLocale(Locale.US)
Escribir 1,75 en Double.parseDouble NumberFormatException parseDouble siempre usa punto
Texto donde se espera un número InputMismatchException Valida con hasNextInt() / hasNextDouble()
No consumir el dato incorrecto al validar Bucle infinito teclado.next() dentro del bucle
printf sin %n Todo en la misma línea Termina el formato con %n
Tipo incorrecto en printf IllegalFormatConversionException %d para enteros, %f para reales
Faltan valores en printf MissingFormatArgumentException Un valor por cada especificador
Querer mostrar % en printf UnknownFormatConversionException o resultado raro Escribe %%
\u mal formado en un comentario illegal unicode escape No escribas \u en comentarios
Crear varios Scanner o cerrarlo Datos perdidos, NoSuchElementException Un único Scanner, sin cerrar
Acceder a args[0] sin argumentos ArrayIndexOutOfBoundsException Comprueba args.length antes

15. Ejercicios

Ejercicio 1: Ficha personal

Pide el nombre completo (puede tener espacios), la edad y la ciudad (también puede tener espacios), y muestra una ficha con los datos alineados. Ten cuidado con el salto de línea pendiente.

Ver solución
import java.util.Scanner;

public class Ficha {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);
        System.out.print("Nombre completo: ");
        String nombre = teclado.nextLine();
        System.out.print("Edad: ");
        int edad = teclado.nextInt();
        teclado.nextLine();                    // descartamos el salto de línea
        System.out.print("Ciudad: ");
        String ciudad = teclado.nextLine();

        System.out.println();
        System.out.println("FICHA");
        System.out.println("Nombre: " + nombre);
        System.out.println("Edad:   " + edad);
        System.out.println("Ciudad: " + ciudad);
    }
}
Nombre completo: Lucía Martín Gil
Edad: 19
Ciudad: San Sebastián

FICHA
Nombre: Lucía Martín Gil
Edad:   19
Ciudad: San Sebastián

Sin el teclado.nextLine() de la línea 10, la ciudad se leería vacía. El nombre no tiene ese problema porque se lee con nextLine() antes de cualquier nextInt().

Ejercicio 2: Conversor de euros a dólares

Pide una cantidad de euros y muéstrala convertida a dólares (usa una constante con un tipo de cambio de 1,08), con 2 decimales y separador de miles, primero con la configuración española y después con la de Estados Unidos.

Ver solución
import java.util.Scanner;

public class Euros {
    public static void main(String[] args) {
        final double DOLARES_POR_EURO = 1.08;   // tipo de cambio de ejemplo
        Scanner teclado = new Scanner(System.in);
        System.out.print("Euros: ");
        double euros = teclado.nextDouble();
        double dolares = euros * DOLARES_POR_EURO;
        System.out.printf("%,.2f € = %,.2f $%n", euros, dolares);
        System.out.printf(java.util.Locale.US, "%,.2f EUR = %,.2f USD%n", euros, dolares);
    }
}
Euros: 1250,5
1.250,50 € = 1.350,54 $
1,250.50 EUR = 1,350.54 USD

En lugar de importar java.util.Locale, aquí lo hemos escrito con su nombre completo (java.util.Locale.US): es otra forma válida de usar una clase de otro paquete.

Ejercicio 3: Tabla de potencias

Muestra una tabla alineada con los números del 1 al 10, su cuadrado, su cubo y su raíz cuadrada con 4 decimales. (Usa un bucle for como el de la tabla de multiplicar del módulo 02.)

Ver solución
public class Potencias {
    public static void main(String[] args) {
        System.out.printf("%3s %6s %8s %10s%n", "n", "n^2", "n^3", "raíz");
        for (int n = 1; n <= 10; n++) {
            System.out.printf("%3d %6d %8d %10.4f%n", n, n * n, n * n * n, Math.sqrt(n));
        }
    }
}
  n    n^2      n^3       raíz
  1      1        1     1,0000
  2      4        8     1,4142
  3      9       27     1,7321
  4     16       64     2,0000
  5     25      125     2,2361
  6     36      216     2,4495
  7     49      343     2,6458
  8     64      512     2,8284
  9     81      729     3,0000
 10    100     1000     3,1623

La cabecera usa los mismos anchos que los datos (3, 6, 8 y 10), pero con %s, para que los títulos queden alineados con sus columnas.

Ejercicio 4: Media de los argumentos

Escribe un programa que reciba cualquier número de notas como argumentos de línea de comandos y muestre cuántas son y su media con 2 decimales. Si no recibe ninguna, debe mostrar un mensaje de uso por la salida de errores.

Ver solución
public class MediaArgs {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.err.println("Uso: java MediaArgs <nota1> <nota2> ...");
            return;
        }
        double suma = 0;
        for (int i = 0; i < args.length; i++) {
            suma = suma + Double.parseDouble(args[i]);
        }
        System.out.printf("Has introducido %d notas. Media: %.2f%n", args.length, suma / args.length);
    }
}
$ java MediaArgs.java 6.5 8 7.25 9
Has introducido 4 notas. Media: 7,69
  • Las notas se escriben con punto porque se convierten con Double.parseDouble.
  • return; termina main (y, por tanto, el programa) en ese punto.
  • El bucle for recorre los argumentos args[0], args[1]… hasta args[args.length - 1] (lo verás en el módulo 08).

Ejercicio 5: Edad validada

Pide una edad hasta que el usuario escriba un número entero entre 0 y 120. Si escribe algo que no es un número, avísale; si el número está fuera de rango, vuelve a preguntar.

Ver solución
import java.util.Scanner;

public class PedirEdad {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);
        int edad = -1;
        while (edad < 0 || edad > 120) {
            System.out.print("Edad (0-120): ");
            while (!teclado.hasNextInt()) {
                System.out.print("Debe ser un número entero. Edad (0-120): ");
                teclado.next();
            }
            edad = teclado.nextInt();
        }
        System.out.println("Edad correcta: " + edad);
    }
}
Edad (0-120): diez
Debe ser un número entero. Edad (0-120): -3
Edad (0-120): 200
Edad (0-120): 35
Edad correcta: 35

Hay dos bucles: el interior (while (!teclado.hasNextInt())) insiste hasta que el dato sea un entero; el exterior repite la pregunta mientras el entero esté fuera de rango. Inicializamos edad a −1 (un valor no válido) para que el bucle exterior se ejecute al menos una vez.

Ejercicio 6: Saludo con IO y forma compacta

Usando la forma compacta de Java 25 y la clase IO, pregunta el nombre y el año de nacimiento, y muestra cuántos años cumple en 2026.

Ver solución

Fichero SaludoIO.java:

void main() {
    String nombre = IO.readln("¿Cómo te llamas? ");
    int nacimiento = Integer.parseInt(IO.readln("¿En qué año naciste? "));
    int edad = 2026 - nacimiento;
    IO.println("Hola, " + nombre + ". En 2026 cumples " + edad + " años.");
}
¿Cómo te llamas? Mario
¿En qué año naciste? 2007
Hola, Mario. En 2026 cumples 19 años.

Sin import, sin Scanner, sin clase y sin static. Como IO.readln lee líneas completas, no hay que preocuparse por el salto de línea pendiente.

Ejercicio 7 (propuesto): Recibo de nómina

Pide el nombre del trabajador (con espacios), el salario bruto mensual y el porcentaje de retención de IRPF. Muestra un recibo con el bruto, la retención de IRPF, la cotización a la Seguridad Social (un 6,47 %, como constante) y el salario neto, con las cantidades alineadas a la derecha, 2 decimales y separador de miles.

Ejercicio 8 (propuesto): Datos con tabuladores y con printf

Muestra una tabla con el nombre, la edad y la ciudad de cuatro personas de dos formas: con \t y con printf y anchos fijos. Incluye una persona con un nombre muy largo (por ejemplo, “Maximiliano Fernández-Villaverde”) y compara qué versión se descuadra.

Ejercicio 9 (propuesto): Conversor de tiempo con argumentos

Escribe un programa que reciba un número de segundos como argumento de línea de comandos y muestre el equivalente en el formato HH:MM:SS con ceros a la izquierda (por ejemplo, 3725 → 01:02:05). Pista: %02d. Si no recibe argumentos, debe mostrar un mensaje de uso por System.err.

Ejercicio 10 (propuesto): Lectura robusta de un precio

Pide un precio aceptando tanto 19,99 como 19.99. Pista: léelo con nextLine(), sustituye la coma por un punto con texto.replace(',', '.') y conviértelo con Double.parseDouble. Muestra el precio con IVA con printf.

Ejercicio 11 (propuesto): Ticket con descuento

Amplía el ticket de la sección 13.3 para que pida, al final, un porcentaje de descuento que se aplica sobre la base imponible antes de calcular el IVA, y muéstralo como una línea más del ticket. Pruébalo pasando los datos desde un fichero con < (o con Get-Content en PowerShell).

Ejercicio 12 (propuesto): Pruebas con tubería

Crea un fichero de texto con datos de prueba incorrectos para la calculadora de la sección 13.1 (letras, un número con punto en lugar de coma, una línea vacía…) y ejecútala redirigiendo la entrada desde ese fichero. Anota qué excepción se produce en cada caso y en qué línea del programa. Después escribe una versión de la calculadora que valide los dos números con hasNextDouble().


Resumen

Concepto Sintaxis / ejemplo Idea clave
Flujos estándar System.in, System.out, System.err Entrada, salida y errores
Mostrar System.out.println(x) / print(x) Con y sin salto de línea
Escapes \n, \t, \", \\, \uXXXX Caracteres especiales en textos
Formato printf("%-10s %5d %8.2f%n", s, n, x) %[opciones][ancho][.precisión]conversión
Especificadores %d, %f, %.2f, %s, %c, %b, %e, %n, %% El tipo debe coincidir
Opciones - izquierda, 0 ceros, + signo, , miles %,d, %05d, %-10s
Formatear a String String.format(...), "...".formatted(...) Mismos especificadores
Configuración regional Locale.of("es", "ES"), Locale.US, useLocale Coma o punto decimal
Scanner Scanner teclado = new Scanner(System.in); import java.util.Scanner;
Leer nextInt(), nextDouble(), next(), nextLine() Tokens frente a líneas
Salto de línea pendiente teclado.nextLine(); tras nextInt() O leer todo con nextLine() + parseInt
Validar while (!teclado.hasNextInt()) teclado.next(); Evita InputMismatchException
Clase IO (Java 25) IO.readln("¿Nombre? "), IO.println(x) Sin import; devuelve String
BufferedReader new BufferedReader(new InputStreamReader(System.in)) readLine(); código antiguo y ficheros
Argumentos java Programa Ana 20 → args[0], args[1] Siempre String; comprobar args.length
Errores System.err.println("...") Canal separado; rojo en IntelliJ
Redirecciones > salida.txt, 2> errores.txt, < datos.txt, \| Probar programas sin teclear