Módulo 04: Entrada y salida por consola
Objetivos del módulo
- Entender los flujos estándar de un programa:
System.in,System.outySystem.err - Mostrar información con
printyprintlny usar las secuencias de escape - Dar formato a la salida con
printf,String.formatyformatted: 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
hasNextInty conocer la excepciónInputMismatchException - Usar la clase
IOde Java 25 como alternativa moderna, y conocerBufferedReader - Recibir argumentos de línea de comandos con
args - Escribir mensajes de error con
System.erry 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.outySystem.err.
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
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
\use procesan antes que el resto del código, incluso dentro de los comentarios. Si escribes en un comentario algo como\useguido 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, usaprintfcon 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:
printfno salta de línea al final (es como%n, que produce el salto de línea correcto de cada sistema operativo. También funciona\n, pero%nes 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ónIllegalFormatConversionException(d != java.lang.Double), yprintf("%.2f", 5)también falla, porque5es unint: escribe5.0o haz un casting. Y si pones más especificadores que valores, obtendrásMissingFormatArgumentException.
4.2. Anatomía de un especificador
Un especificador completo tiene esta forma: %[opciones][ancho][.precisión]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 queprintf, pero devuelve unString."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.formatyformattedusan exactamente los mismos especificadores. Aprende uno y sabrás los tres. Y recuerda: formatear solo cambia cómo se muestra el número; la variablenotasigue 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
Localerepresenta una configuración regional: idioma y país (es_ESes 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
}
}
import java.util.Scanner;:Scannerestá en el paquetejava.util, que no se importa automáticamente (a diferencia dejava.lang, donde estánSystem,StringoMath). Sin elimport, obtendríascannot find symbol.new Scanner(System.in)crea un objetoScannerque lee de la entrada estándar. Le damos el nombreteclado(otros nombres habituales:sc,entrada,in).- 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
ScannersobreSystem.inen todo el programa y reutilízalo. Y no lo cierres conteclado.close()si vas a seguir leyendo: cerrar elScannercierra tambiénSystem.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:
Scannerdivide 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 tresnextInt().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.
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 escribir1.75(con punto), mientras quenextDouble()en un sistema en español espera1,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 tokenveinteuna 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 conInteger.parseIntoDouble.parseDouble(que usa punto decimal, como se ve con1.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.readlndevuelvenull, 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.outyScanner, que son lo que encontrarás en exámenes, libros y proyectos existentes.IOes perfecta para pruebas rápidas y para programas compactos convoid 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.lengthes 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.errsale 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;terminamain(y, por tanto, el programa) en ese punto.- El bucle
forrecorre los argumentosargs[0],args[1]… hastaargs[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 |