Hasta ahora tus programas han sido monólogos: los datos estaban escritos a fuego en el código y la salida se limitaba a concatenar cadenas con +, produciendo importes como 13.875 EUR que ningún recibo aceptaría. Esta lección cierra las dos carencias. Aprenderás a dar formato profesional a la salida con printf (alineaciones, anchos, decimales controlados) y a leer datos del usuario con Scanner, incluido el famoso problema de mezclar nextInt() con nextLine() que desconcierta a todo el mundo la primera vez. Al terminar, BiblioTech podrá conversar con quien lo usa y emitir una ficha de libro con aspecto de documento real.
Todo el código de esta lección es lineal: sin condicionales ni bucles, que llegan en el módulo 2.
Contenido
- La salida estándar:
System.out printfrente aprintlnprintfy los especificadores de formato- Anchos, alineación y otros modificadores
String.format: el mismo formato, en una variableSystem.erry cuándo usarlo- Leer datos con
Scanner - Los métodos de lectura y qué devuelve cada uno
- El problema clásico: mezclar
nextInt()connextLine() - Cerrar el
Scanner - Conversión de texto a número:
parseIntyparseDouble - La ficha de libro de BiblioTech
- Errores Comunes y Consejos
- Ejercicios
- La salida estándar:
System.out
System.outSystem.out es un objeto que representa la salida estándar: normalmente, la consola. Está siempre disponible sin necesidad de ningún import, porque la clase System pertenece a java.lang.
Desglosemos la expresión que llevas escribiendo desde la primera lección:
Systemes una clase dejava.langque da acceso a recursos del sistema.outes un campo de esa clase: un objetoPrintStreamconectado a la consola.printlnes un método de ese objeto.
Los tres métodos que usarás:
| Método | Qué hace |
|---|---|
print(x) |
Escribe x sin salto de línea |
println(x) |
Escribe x y salta de línea |
printf(formato, args...) |
Escribe con formato controlado, sin salto automático |
print frente a println
print frente a printlnLa diferencia es únicamente el salto de línea final, pero cambia mucho la composición de la salida:
System.out.print("Titulo: ");
System.out.print("Java Efectivo");
System.out.println(); // println() sin argumentos solo salta de linea
System.out.println("ISBN: 978-0000000001");Salida:
Ambos métodos aceptan cualquier tipo: String, números, boolean, char. Java convierte automáticamente el valor a texto.
System.out.println(2018); // 2018
System.out.println(3.75); // 3.75
System.out.println(true); // true
System.out.println('J'); // JY admiten concatenación con +, que es lo que has estado usando:
String titulo = "Java Efectivo";
int anioPublicacion = 2018;
System.out.println("Libro: " + titulo + " (" + anioPublicacion + ")");Este estilo funciona, pero se vuelve ilegible en cuanto hay cuatro o cinco variables, y no permite controlar el formato: no puedes decir "muéstrame este double con exactamente dos decimales". Para eso está printf.
printf y los especificadores de formato
printf y los especificadores de formatoprintf (de print formatted) recibe una cadena de formato con marcadores que empiezan por %, seguida de los valores que rellenarán esos marcadores, en orden.
String titulo = "Java Efectivo";
double multa = 13.875;
System.out.printf("El libro %s acumula una multa de %.2f EUR%n", titulo, multa);Salida:
Fíjate en lo que acaba de ocurrir: 13.875 se ha mostrado como 13.88, redondeado a dos decimales. Ese control es exactamente lo que faltaba en la lección anterior.
Los especificadores esenciales
| Especificador | Tipo que espera | Ejemplo de uso | Resultado |
|---|---|---|---|
%s |
Cualquier cosa (texto) | printf("%s", "Java Efectivo") |
Java Efectivo |
%d |
Entero (int, long) |
printf("%d", 2018) |
2018 |
%f |
Decimal (double, float) |
printf("%f", 3.5) |
3,500000 |
%.2f |
Decimal con 2 decimales | printf("%.2f", 13.875) |
13,88 |
%b |
Booleano | printf("%b", true) |
true |
%c |
Carácter | printf("%c", 'J') |
J |
%n |
Salto de línea portable | printf("Fin%n") |
salta de línea |
%% |
Un símbolo % literal |
printf("100%%") |
100% |
Tres puntos críticos:
%fpor defecto muestra SEIS decimales.printf("%f", 3.5)imprime3,500000. Casi siempre querrás%.2f.- Usa
%ny no\n.%ngenera el salto de línea propio del sistema operativo (\nen Unix,\r\nen Windows);\nsiempre genera el de Unix. En consola apenas se nota, pero es la práctica correcta. printfno salta de línea por sí solo. Si olvidas el%nfinal, la siguiente salida se pegará a la anterior.
El detalle de la coma decimal
Habrás notado que la tabla muestra 13,88 con coma. printf respeta la configuración regional del sistema: en España el separador decimal es la coma. Si necesitas un punto de forma determinista (por ejemplo, para generar un fichero CSV), puedes indicar la configuración regional explícitamente:
Es un matiz que conviene conocer ahora para que no te sorprenda la salida en tu máquina.
Varios valores a la vez
Los valores se consumen en orden, de izquierda a derecha:
String empleado = "Marta Ruiz";
String titulo = "Java Efectivo";
int diasRetraso = 12;
double multa = 3.0;
System.out.printf("%s tiene %s con %d dias de retraso: %.2f EUR%n",
empleado, titulo, diasRetraso, multa);Salida:
Si el número de marcadores no coincide con el de valores, o los tipos no encajan, el programa falla en ejecución con MissingFormatArgumentException o IllegalFormatConversionException. El caso más frecuente es usar %d para un double: %d es solo para enteros.
- Anchos, alineación y otros modificadores
Aquí es donde printf deja de ser una comodidad y se convierte en una herramienta: puede alinear columnas.
La estructura completa de un especificador es:
| Modificador | Significado | Ejemplo | Resultado (con | marcando los bordes) |
|---|---|---|---|
%10s |
Ancho 10, alineado a la derecha | printf("|%10s|", "Java") |
| Java| |
%-10s |
Ancho 10, alineado a la izquierda | printf("|%-10s|", "Java") |
|Java | |
%5d |
Entero en ancho 5, a la derecha | printf("|%5d|", 12) |
| 12| |
%-5d |
Entero en ancho 5, a la izquierda | printf("|%-5d|", 12) |
|12 | |
%05d |
Rellena con ceros por la izquierda | printf("%05d", 42) |
00042 |
%8.2f |
Ancho 8 total, 2 decimales | printf("|%8.2f|", 13.875) |
| 13,88| |
%,d |
Separador de millares | printf("%,d", 1250000) |
1.250.000 |
%+d |
Muestra siempre el signo | printf("%+d", 12) |
+12 |
Puntos a recordar:
- El ancho es un mínimo, no un máximo. Si el texto es más largo, se muestra completo y desborda la columna.
- El guion activa la alineación a la izquierda. Sin él, todo se alinea a la derecha.
- Convención habitual: texto a la izquierda, números a la derecha. Los números alineados a la derecha permiten comparar magnitudes de un vistazo, porque las unidades quedan en la misma columna.
Aplicado a una tabla del catálogo de BiblioTech:
System.out.printf("%-22s %-16s %6s %10s%n", "TITULO", "AUTOR", "ANIO", "MULTA");
System.out.println("-".repeat(58));
System.out.printf("%-22s %-16s %6d %10.2f%n", "Java Efectivo", "Joshua Bloch", 2018, 3.0);
System.out.printf("%-22s %-16s %6d %10.2f%n", "Patrones de Diseno", "Erich Gamma", 1994, 13.88);
System.out.printf("%-22s %-16s %6d %10.2f%n", "Refactorizacion", "Martin Fowler", 1999, 0.0);Salida:
TITULO AUTOR ANIO MULTA ---------------------------------------------------------- Java Efectivo Joshua Bloch 2018 3,00 Patrones de Diseno Erich Gamma 1994 13,88 Refactorizacion Martin Fowler 1999 0,00
Con println y concatenación, esta alineación sería prácticamente imposible. Y observa el truco de "-".repeat(58): el método repeat de String (Java 11+) repite una cadena, perfecto para líneas separadoras.
String.format: el mismo formato, en una variable
String.format: el mismo formato, en una variableA veces no quieres imprimir el texto, sino guardarlo. String.format usa exactamente la misma sintaxis que printf pero devuelve un String en lugar de escribirlo:
double multa = 13.875;
String lineaRecibo = String.format("Multa acumulada: %.2f EUR", multa);
System.out.println(lineaRecibo);Las dos formas siguientes son equivalentes:
System.out.printf("Multa: %.2f EUR%n", multa);
System.out.println(String.format("Multa: %.2f EUR", multa));String.format es la opción cuando el texto formateado debe reutilizarse: guardarse en un fichero (módulo 7), enviarse por red (módulo 9) o componer un mensaje que se mostrará más tarde.
System.err y cuándo usarlo
System.err y cuándo usarloJunto a la salida estándar existe la salida de error estándar, System.err, con los mismos métodos:
System.out.println("Prestamo registrado correctamente");
System.err.println("AVISO: el ISBN introducido no tiene el formato esperado");En la consola del IDE, System.err suele aparecer en rojo. Pero su verdadera razón de ser es que son dos canales independientes que el sistema operativo puede redirigir por separado:
# Solo la salida normal va al fichero; los errores siguen viendose en pantalla
java BiblioTechApp > recibos.txt
# Cada canal a un fichero distinto
java BiblioTechApp > recibos.txt 2> errores.logEsto es lo que permite que un proceso automatizado guarde los datos limpios en un sitio y los problemas en otro.
| Canal | Se usa para | Ejemplo en BiblioTech |
|---|---|---|
System.out |
Resultados, información normal | El recibo del préstamo |
System.err |
Errores, avisos, diagnósticos | "No se ha podido leer el catálogo" |
Un matiz técnico: System.err no usa búfer y System.out sí, por lo que al mezclarlos los mensajes pueden aparecer desordenados en pantalla. No es un fallo tuyo.
En aplicaciones profesionales, System.err acaba sustituido por un sistema de logging con niveles (INFO, WARN, ERROR), que se estudia en la lección 06-07. Para el módulo 1, System.err es suficiente.
- Leer datos con
Scanner
ScannerScanner es la clase estándar para leer entrada del usuario. Vive en java.util, así que necesita import:
import java.util.Scanner;
public class LecturaBiblioTech {
public static void main(String[] args) {
// Creamos un Scanner conectado a la entrada estandar (el teclado)
Scanner entrada = new Scanner(System.in);
System.out.print("Nombre del empleado: ");
String empleado = entrada.nextLine();
System.out.println("Bienvenido a BiblioTech, " + empleado);
entrada.close();
}
}Analicemos la línea clave:
new Scanner(...)crea un objeto nuevo.newes el operador de creación de objetos; lo estudiarás a fondo en el módulo 3, pero aquí ya lo necesitas.System.ines la entrada estándar: el teclado, análogo aSystem.outpara la salida.entradaes el nombre que le damos. Podría sersc,tecladoolector; conviene que sea descriptivo.
Un detalle de usabilidad: para pedir datos se usa System.out.print (sin ln), de modo que el cursor se quede en la misma línea que la pregunta. Es un detalle pequeño con mucho impacto en la sensación de calidad del programa.
- Los métodos de lectura y qué devuelve cada uno
| Método | Devuelve | Qué lee |
|---|---|---|
nextLine() |
String |
Toda la línea, incluidos espacios, hasta el Enter |
next() |
String |
Una sola palabra (hasta el primer espacio) |
nextInt() |
int |
El siguiente entero |
nextDouble() |
double |
El siguiente decimal |
nextBoolean() |
boolean |
true o false |
hasNextInt() |
boolean |
¿Lo siguiente es un entero? No consume nada |
hasNextLine() |
boolean |
¿Queda alguna línea? No consume nada |
La diferencia entre nextLine() y next() importa mucho en BiblioTech, donde los títulos y nombres llevan espacios:
System.out.print("Titulo del libro: ");
String titulo = entrada.nextLine(); // "Java Efectivo" completo
System.out.print("Titulo del libro: ");
String palabra = entrada.next(); // solo "Java"; "Efectivo" queda pendienteRegla práctica: para nombres y títulos, usa siempre nextLine().
Los métodos hasNextXxx() son especiales: preguntan sin consumir. Sirven para comprobar si lo que viene es del tipo esperado antes de leerlo:
System.out.print("Dias transcurridos: ");
boolean esNumeroValido = entrada.hasNextInt();
System.out.println("¿La entrada es un entero? " + esNumeroValido);Su utilidad plena requiere condicionales (módulo 2) para reaccionar al resultado, y bucles para insistir hasta obtener un dato válido (lección 02-02). Por ahora quédate con que existen y con qué hacen.
- El problema clásico: mezclar
nextInt() con nextLine()
nextInt() con nextLine()Este es el bug que sufre absolutamente todo el mundo al aprender Java. Observa:
Scanner entrada = new Scanner(System.in);
System.out.print("Dias transcurridos: ");
int dias = entrada.nextInt();
System.out.print("Nombre del empleado: ");
String empleado = entrada.nextLine(); // <- NO ESPERA: se salta la pregunta
System.out.println("[" + empleado + "]"); // imprime []El programa pide los días, y después muestra la segunda pregunta y sigue de largo sin dejarte escribir nada. empleado queda vacío.
Por qué ocurre
Scanner no lee "por preguntas", sino de un flujo continuo de caracteres. Cuando escribes 27 y pulsas Enter, en ese flujo hay:
nextInt() consume los caracteres del número (27) y se detiene justo antes del \n, dejándolo en el flujo. Después, nextLine() hace lo que le corresponde: lee hasta el siguiente salto de línea. Como el salto está ahí mismo, devuelve una cadena vacía inmediatamente, sin esperar a nadie.
flowchart TD
A["El usuario escribe: 27 + Enter"] --> B["Flujo de entrada: '27\\n'"]
B --> C["nextInt() consume '27'<br/>y DEJA el '\\n' pendiente"]
C --> D["nextLine() encuentra el '\\n'<br/>y devuelve cadena vacía sin esperar"]
D --> E["Parece que el programa<br/>se ha saltado la pregunta"]
Las dos soluciones
Solución 1: consumir el salto pendiente con un nextLine() extra.
System.out.print("Dias transcurridos: ");
int dias = entrada.nextInt();
entrada.nextLine(); // consume el \n que dejo nextInt()
System.out.print("Nombre del empleado: ");
String empleado = entrada.nextLine(); // ahora SI esperaSolución 2 (la recomendada): leer todo con nextLine() y convertir.
System.out.print("Dias transcurridos: ");
int dias = Integer.parseInt(entrada.nextLine()); // lee la linea ENTERA y la convierte
System.out.print("Nombre del empleado: ");
String empleado = entrada.nextLine(); // funciona sin sorpresasComparación de ambos enfoques:
| Enfoque | Ventajas | Inconvenientes |
|---|---|---|
nextInt() + nextLine() extra |
Menos escritura | Fácil de olvidar; hay que recordar dónde ponerlo |
Solo nextLine() + parseInt |
Coherente y predecible; un método para todo | Una llamada de conversión por dato |
Este curso adopta la segunda opción en BiblioTechApp. Cuando toda la lectura pasa por nextLine(), el problema desaparece por construcción y el código es uniforme.
- Cerrar el
Scanner
ScannerScanner es un recurso del sistema y debe cerrarse cuando ya no se necesita:
Ponlo al final de main. Si no lo haces, el compilador y el IDE mostrarán un aviso de "recurso no cerrado" (resource leak).
Una advertencia importante: cerrar un Scanner creado sobre System.in cierra también System.in. A partir de ese momento no puedes crear otro Scanner para leer del teclado en el mismo programa. Por eso la regla es: un solo Scanner para toda la aplicación, creado al principio y cerrado al final.
En el módulo 6 verás try-with-resources, que cierra los recursos automáticamente y es la forma profesional de hacerlo.
- Conversión de texto a número:
parseInt y parseDouble
parseInt y parseDoubleTodo lo que llega de la consola es texto, aunque el usuario escriba dígitos. Para operar con ello hay que convertirlo:
String textoDias = "27";
int dias = Integer.parseInt(textoDias); // 27 como numero
String textoTarifa = "0.25";
double tarifa = Double.parseDouble(textoTarifa); // 0.25 como numeroEs la razón por la que estas dos líneas se comportan de forma tan distinta:
System.out.println("27" + 15); // "2715" -> concatenacion de texto
System.out.println(Integer.parseInt("27") + 15); // 42 -> suma de numerosQué pasa si el texto no es un número
El programa se detiene con un error de ejecución:
Exception in thread "main" java.lang.NumberFormatException: For input string: "veintisiete" at java.base/java.lang.Integer.parseInt(Integer.java:668) at BiblioTechApp.main(BiblioTechApp.java:12)
Esto es una excepción, el mecanismo de Java para señalar errores en ejecución. Capturarlas y responder a ellas de forma elegante (volver a pedir el dato, mostrar un mensaje amable) es el objeto del módulo 6. Por ahora quédate con dos ideas:
- Sabes qué significa ese error si aparece.
- Los programas de este módulo asumen que el usuario introduce datos correctos. Es una limitación real que reconoceremos explícitamente en la lección siguiente.
Un detalle útil: Double.parseDouble espera el punto como separador decimal, no la coma. Double.parseDouble("0,25") falla; Double.parseDouble("0.25") funciona. Es la asimetría inversa a la de printf, y conviene tenerla presente al probar tus programas.
- La ficha de libro de BiblioTech
Juntemos todo. Este programa pide por consola los datos de un libro del catálogo y emite una ficha formateada:
import java.util.Scanner;
public class FichaLibroBiblioTech {
public static void main(String[] args) {
// Un unico Scanner para toda la aplicacion.
Scanner entrada = new Scanner(System.in);
// --- Constantes de negocio ---
final String NOMBRE_EMPRESA = "Nexus Software";
final int ANIO_ACTUAL = 2026;
final int ANCHO_FICHA = 46;
// --- Cabecera de bienvenida ---
System.out.println("=".repeat(ANCHO_FICHA));
System.out.printf(" BiblioTech - %s%n", NOMBRE_EMPRESA);
System.out.println(" Alta de libro en el catalogo");
System.out.println("=".repeat(ANCHO_FICHA));
System.out.println();
// --- Recogida de datos ---
// TODA la lectura se hace con nextLine() para evitar el problema
// del salto de linea pendiente que deja nextInt().
System.out.print("Titulo del libro : ");
String titulo = entrada.nextLine();
System.out.print("Autor : ");
String autor = entrada.nextLine();
System.out.print("ISBN : ");
String isbn = entrada.nextLine();
System.out.print("Anio de publicacion: ");
int anioPublicacion = Integer.parseInt(entrada.nextLine());
System.out.print("Numero de paginas : ");
int paginas = Integer.parseInt(entrada.nextLine());
System.out.print("Precio de compra : ");
double precio = Double.parseDouble(entrada.nextLine());
// --- Calculos derivados ---
int antiguedad = ANIO_ACTUAL - anioPublicacion;
double precioPorPagina = precio / paginas; // double / int -> double
boolean disponible = true; // todo libro nuevo entra disponible
// --- Ficha formateada ---
System.out.println();
System.out.println("=".repeat(ANCHO_FICHA));
System.out.println(" FICHA DE CATALOGO");
System.out.println("=".repeat(ANCHO_FICHA));
System.out.printf("%-20s %s%n", "Titulo:", titulo);
System.out.printf("%-20s %s%n", "Autor:", autor);
System.out.printf("%-20s %s%n", "ISBN:", isbn);
System.out.printf("%-20s %d%n", "Publicacion:", anioPublicacion);
System.out.printf("%-20s %d anios%n", "Antiguedad:", antiguedad);
System.out.printf("%-20s %,d%n", "Paginas:", paginas);
System.out.printf("%-20s %8.2f EUR%n", "Precio:", precio);
System.out.printf("%-20s %8.4f EUR%n", "Precio/pagina:", precioPorPagina);
System.out.printf("%-20s %b%n", "Disponible:", disponible);
System.out.println("=".repeat(ANCHO_FICHA));
System.err.println("[info] Ficha generada sin validacion de datos.");
entrada.close();
}
}Ejemplo de ejecución (lo que escribe el usuario aparece tras los dos puntos):
============================================== BiblioTech - Nexus Software Alta de libro en el catalogo ============================================== Titulo del libro : Java Efectivo Autor : Joshua Bloch ISBN : 978-0000000001 Anio de publicacion: 2018 Numero de paginas : 416 Precio de compra : 45.9 ============================================== FICHA DE CATALOGO ============================================== Titulo: Java Efectivo Autor: Joshua Bloch ISBN: 978-0000000001 Publicacion: 2018 Antiguedad: 8 anios Paginas: 416 Precio: 45,90 EUR Precio/pagina: 0,1103 EUR Disponible: true ==============================================
Cinco decisiones de diseño que merece la pena señalar:
- Un único
Scanner, creado al principio y cerrado al final. - Toda la lectura con
nextLine(), con conversión explícita. Nunca se mezcla connextInt(). System.out.printpara las preguntas, para que el cursor se quede en la línea.%-20sen las etiquetas, que crea una columna izquierda perfectamente alineada.%8.2fpara los importes: dos decimales y ancho fijo, de forma que las cifras queden alineadas por la derecha.
Errores Comunes y Consejos
- Olvidar
import java.util.Scanner;. Producecannot find symbol: class Scanner. Es el import que más olvidarás en todo el curso. - Mezclar
nextInt()connextLine(). El programa se salta preguntas. UsanextLine()para todo y convierte conparseInt. - Usar
next()para leer títulos o nombres. Se corta en el primer espacio: "Marta Ruiz" se convierte en "Marta". - Usar
%dpara undouble. LanzaIllegalFormatConversionException.%des solo para enteros; para decimales,%f. - Que
%fmuestre seis decimales. Es su comportamiento por defecto; especifica siempre la precisión:%.2f. - Olvidar
%nal final de unprintf. Todas las líneas se pegan. - Crear un
Scannernuevo después de cerrar otro sobreSystem.in. No funciona:System.inqueda cerrado. Un soloScannerpor programa. - Introducir
0,25en lugar de0.25.Double.parseDoubleespera el punto y lanzaNumberFormatExceptioncon la coma. - Consejo: define constantes para los anchos de formato (
ANCHO_FICHA) en vez de repetir números. Cambiar el diseño se convierte en cambiar una línea. - Consejo:
"=".repeat(46)es mucho más limpio que escribir 46 signos igual a mano, y no te equivocarás al contarlos. - Consejo: cuando una salida no quede alineada, imprime una línea de referencia con
|en los bordes (printf("|%-20s|%n", texto)) para ver exactamente dónde caen las columnas.
Ejercicios
Ejercicio 1: Predecir el comportamiento
Analiza este programa sin ejecutarlo. Indica qué se imprime, qué datos consigue leer realmente y por qué. Después escribe dos versiones corregidas: una con la técnica del nextLine() extra y otra leyendo todo con nextLine().
import java.util.Scanner;
public class PruebaScanner {
public static void main(String[] args) {
Scanner entrada = new Scanner(System.in);
System.out.print("Dias transcurridos: ");
int dias = entrada.nextInt();
System.out.print("Nombre del empleado: ");
String empleado = entrada.nextLine();
System.out.print("Titulo del libro: ");
String titulo = entrada.nextLine();
System.out.println("Empleado: [" + empleado + "]");
System.out.println("Titulo: [" + titulo + "]");
System.out.println("Dias: " + dias);
entrada.close();
}
}Ejercicio 2: Tabla del catálogo alineada
Escribe la clase TablaCatalogo que imprima, sin pedir nada al usuario, la siguiente tabla exacta con los tres libros de BiblioTech. Usa solo printf y "-".repeat(...).
+------------------------+------------------+-------+------------+ | TITULO | AUTOR | ANIO | PRECIO | +------------------------+------------------+-------+------------+ | Java Efectivo | Joshua Bloch | 2018 | 45,90 | | Patrones de Diseno | Erich Gamma | 1994 | 52,00 | | Refactorizacion | Martin Fowler | 1999 | 38,75 | +------------------------+------------------+-------+------------+ | TOTAL | | 136,65 | +------------------------+------------------+-------+------------+
Pista: define constantes con los anchos de cada columna y construye la línea separadora con repeat.
Ejercicio 3: Registro de devolución
Escribe la clase RegistroDevolucion que pida por consola:
- Nombre del empleado
- Título del libro
- ISBN
- Días transcurridos desde el préstamo
- Tarifa diaria en euros
y muestre un recibo formateado con: los datos introducidos, los días de retraso (saturados a cero con el ternario), el retraso expresado en semanas y días, la multa con dos decimales y una etiqueta de estado (EN PLAZO / CON RETRASO) obtenida con el operador ternario. Usa System.err para emitir un aviso de que el programa no valida las entradas. Sin if ni bucles.
Soluciones
Solución 1
Comportamiento observado. El programa muestra las tres preguntas, pero solo te deja escribir dos veces: los días y, después, lo que acabará siendo el título. La salida es algo como:
Dias transcurridos: 27 Nombre del empleado: Titulo del libro: Marta Ruiz Empleado: [] Titulo: [Marta Ruiz] Dias: 27
Por qué. nextInt() consume los caracteres 27 pero deja el \n del Enter en el flujo de entrada. El primer nextLine() encuentra ese salto inmediatamente y devuelve una cadena vacía sin esperar, por lo que empleado queda a "" y la pregunta del nombre pasa de largo. El segundo nextLine() sí espera, y lo que escribes ahí (aunque sea el nombre) acaba en titulo. Los datos quedan desplazados una posición.
Corrección 1: nextLine() extra.
import java.util.Scanner;
public class PruebaScannerCorregida1 {
public static void main(String[] args) {
Scanner entrada = new Scanner(System.in);
System.out.print("Dias transcurridos: ");
int dias = entrada.nextInt();
entrada.nextLine(); // <- CLAVE: consume el \n pendiente que dejo nextInt()
System.out.print("Nombre del empleado: ");
String empleado = entrada.nextLine();
System.out.print("Titulo del libro: ");
String titulo = entrada.nextLine();
System.out.println("Empleado: [" + empleado + "]");
System.out.println("Titulo: [" + titulo + "]");
System.out.println("Dias: " + dias);
entrada.close();
}
}Corrección 2: todo con nextLine() (recomendada).
import java.util.Scanner;
public class PruebaScannerCorregida2 {
public static void main(String[] args) {
Scanner entrada = new Scanner(System.in);
System.out.print("Dias transcurridos: ");
// nextLine() consume la linea COMPLETA, salto incluido.
// parseInt convierte despues ese texto en numero.
int dias = Integer.parseInt(entrada.nextLine());
System.out.print("Nombre del empleado: ");
String empleado = entrada.nextLine();
System.out.print("Titulo del libro: ");
String titulo = entrada.nextLine();
System.out.println("Empleado: [" + empleado + "]");
System.out.println("Titulo: [" + titulo + "]");
System.out.println("Dias: " + dias);
entrada.close();
}
}La segunda corrección es preferible porque elimina la causa en lugar de parchear el síntoma: si todas las lecturas usan el mismo método, nunca queda nada pendiente en el flujo.
Solución 2
public class TablaCatalogo {
public static void main(String[] args) {
// Anchos de columna centralizados en constantes:
// cambiar el diseno es cambiar estos numeros.
final int ANCHO_TITULO = 22;
final int ANCHO_AUTOR = 16;
final int ANCHO_ANIO = 5;
final int ANCHO_PRECIO = 10;
// La linea separadora se construye una vez y se reutiliza.
// Los +2 corresponden a los espacios que rodean cada celda.
String separador = "+" + "-".repeat(ANCHO_TITULO + 2)
+ "+" + "-".repeat(ANCHO_AUTOR + 2)
+ "+" + "-".repeat(ANCHO_ANIO + 2)
+ "+" + "-".repeat(ANCHO_PRECIO + 2)
+ "+";
// Datos del catalogo
double precioJava = 45.90;
double precioPatrones = 52.00;
double precioRefactor = 38.75;
double total = precioJava + precioPatrones + precioRefactor;
System.out.println(separador);
// Cabecera: texto a la izquierda (-), numeros a la derecha (sin -)
System.out.printf("| %-22s | %-16s | %5s | %10s |%n",
"TITULO", "AUTOR", "ANIO", "PRECIO");
System.out.println(separador);
// Filas de datos. %5d alinea los anios, %10.2f alinea los importes
// por la derecha con exactamente dos decimales.
System.out.printf("| %-22s | %-16s | %5d | %10.2f |%n",
"Java Efectivo", "Joshua Bloch", 2018, precioJava);
System.out.printf("| %-22s | %-16s | %5d | %10.2f |%n",
"Patrones de Diseno", "Erich Gamma", 1994, precioPatrones);
System.out.printf("| %-22s | %-16s | %5d | %10.2f |%n",
"Refactorizacion", "Martin Fowler", 1999, precioRefactor);
System.out.println(separador);
// Fila de total: fusionamos visualmente las dos primeras columnas
// usando un ancho igual a la suma de ambas mas el separador intermedio.
System.out.printf("| %-41s | %5s | %10.2f |%n", "TOTAL", "", total);
System.out.println(separador);
}
}Salida:
+------------------------+------------------+-------+------------+ | TITULO | AUTOR | ANIO | PRECIO | +------------------------+------------------+-------+------------+ | Java Efectivo | Joshua Bloch | 2018 | 45,90 | | Patrones de Diseno | Erich Gamma | 1994 | 52,00 | | Refactorizacion | Martin Fowler | 1999 | 38,75 | +------------------------+------------------+-------+------------+ | TOTAL | | 136,65 | +------------------------+------------------+-------+------------+
El detalle a interiorizar es el 41 de la fila del total: sale de 22 + 3 + 16 (los dos anchos más el " | " que los separaba). Al fusionar celdas hay que sumar los anchos y los separadores intermedios.
Solución 3
import java.util.Scanner;
public class RegistroDevolucion {
public static void main(String[] args) {
Scanner entrada = new Scanner(System.in);
// === Reglas de negocio de BiblioTech ===
final String NOMBRE_EMPRESA = "Nexus Software";
final int DIAS_PRESTAMO = 15;
final int ANCHO = 48;
// === Cabecera ===
System.out.println("=".repeat(ANCHO));
System.out.printf(" BiblioTech - %s%n", NOMBRE_EMPRESA);
System.out.println(" Registro de devolucion");
System.out.println("=".repeat(ANCHO));
System.out.println();
// === Recogida de datos (todo con nextLine) ===
System.out.print("Empleado : ");
String empleado = entrada.nextLine();
System.out.print("Titulo del libro : ");
String titulo = entrada.nextLine();
System.out.print("ISBN : ");
String isbn = entrada.nextLine();
System.out.print("Dias transcurridos : ");
int diasTranscurridos = Integer.parseInt(entrada.nextLine());
System.out.print("Tarifa diaria (EUR): ");
double tarifaDiaria = Double.parseDouble(entrada.nextLine());
// === Calculos ===
// El ternario satura a cero: una devolucion anticipada
// nunca puede producir un retraso negativo.
int diasRetraso = diasTranscurridos > DIAS_PRESTAMO
? diasTranscurridos - DIAS_PRESTAMO
: 0;
int semanas = diasRetraso / 7; // cociente entero
int diasSueltos = diasRetraso % 7; // resto
double multa = diasRetraso * tarifaDiaria; // int * double -> double
String estado = diasRetraso > 0 ? "CON RETRASO" : "EN PLAZO";
// === Recibo ===
System.out.println();
System.out.println("=".repeat(ANCHO));
System.out.println(" RECIBO DE DEVOLUCION");
System.out.println("=".repeat(ANCHO));
System.out.printf("%-22s %s%n", "Empleado:", empleado);
System.out.printf("%-22s %s%n", "Libro:", titulo);
System.out.printf("%-22s %s%n", "ISBN:", isbn);
System.out.println("-".repeat(ANCHO));
System.out.printf("%-22s %5d dias%n", "Prestamo estandar:", DIAS_PRESTAMO);
System.out.printf("%-22s %5d dias%n", "Transcurridos:", diasTranscurridos);
System.out.printf("%-22s %5d dias (%d sem. y %d dias)%n",
"Retraso:", diasRetraso, semanas, diasSueltos);
System.out.println("-".repeat(ANCHO));
System.out.printf("%-22s %8.2f EUR%n", "Tarifa diaria:", tarifaDiaria);
System.out.printf("%-22s %8.2f EUR%n", "MULTA A PAGAR:", multa);
System.out.printf("%-22s %s%n", "Estado:", estado);
System.out.println("=".repeat(ANCHO));
// Aviso por el canal de error: no forma parte del recibo.
System.err.println("[aviso] Este programa no valida las entradas introducidas.");
entrada.close();
}
}Ejemplo de ejecución:
================================================ BiblioTech - Nexus Software Registro de devolucion ================================================ Empleado : Nuria Vidal Titulo del libro : Patrones de Diseno ISBN : 978-0000000002 Dias transcurridos : 34 Tarifa diaria (EUR): 0.25 ================================================ RECIBO DE DEVOLUCION ================================================ Empleado: Nuria Vidal Libro: Patrones de Diseno ISBN: 978-0000000002 ------------------------------------------------ Prestamo estandar: 15 dias Transcurridos: 34 dias Retraso: 19 dias (2 sem. y 5 dias) ------------------------------------------------ Tarifa diaria: 0,25 EUR MULTA A PAGAR: 4,75 EUR Estado: CON RETRASO ================================================
Conclusión
Tus programas ya se comunican en las dos direcciones. Por el lado de la salida, dominas print, println y sobre todo printf con sus especificadores (%s, %d, %.2f, %n), sus anchos y su alineación, lo que te permite construir fichas y tablas con aspecto profesional; conoces String.format para guardar ese texto en lugar de imprimirlo, y sabes que System.err es un canal aparte para avisos y errores. Por el lado de la entrada, sabes crear un Scanner sobre System.in, cuándo usar nextLine frente a next, por qué mezclar nextInt() con nextLine() rompe el flujo y cómo evitarlo leyendo todo como línea y convirtiendo con Integer.parseInt y Double.parseDouble, y por qué el Scanner debe crearse una vez y cerrarse al final.
Con esto tienes todas las piezas del módulo: sintaxis, variables, tipos, operadores y entrada/salida. En la siguiente lección, Tu Primer Programa Completo: BiblioTech, las montarás todas juntas en BiblioTechApp: un programa que da la bienvenida, recoge los datos de un préstamo, calcula el retraso y la multa, y emite un recibo formateado. Lo construiremos por versiones sucesivas, lo compilarás y ejecutarás, comprobarás su salida con dos juegos de datos de prueba y analizarás con honestidad qué no sabe hacer todavía y qué módulo lo resolverá.
Curso de Programación en Java
Módulo 1: Introducción a Java
- Introducción a Java
- Configuración del Entorno de Desarrollo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Operadores
- Entrada y Salida por Consola
- Tu Primer Programa Completo: BiblioTech
Módulo 2: Flujo de Control
- Sentencias Condicionales
- Bucles
- Sentencias Switch
- Break y Continue
- Depuración y Trazas de Ejecución
- Proyecto: Menú Interactivo de BiblioTech
Módulo 3: Programación Orientada a Objetos
- Introducción a la POO
- Clases y Objetos
- Métodos
- Constructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
- La Clase Object: equals, hashCode y toString
Módulo 4: Programación Orientada a Objetos Avanzada
- Interfaces
- Clases Abstractas
- Clases Internas
- Clases Anónimas
- Expresiones Lambda
- Interfaces Funcionales y Referencias a Métodos
- Enumeraciones y Registros
Módulo 5: Estructuras de Datos y Colecciones
- Arreglos
- El Framework de Colecciones
- ArrayList
- LinkedList
- HashMap
- HashSet
- Cola y Deque
- Pila
- Ordenación y Búsqueda en Colecciones
Módulo 6: Manejo de Excepciones
- Introducción a las Excepciones
- Bloque Try-Catch
- Throw y Throws
- Excepciones Personalizadas
- Bloque Finally
- Try-with-resources y AutoCloseable
- Estrategias de Manejo de Errores y Logging
Módulo 7: Entrada/Salida de Archivos
- Lectura de Archivos
- Escritura de Archivos
- Flujos de Archivos
- BufferedReader y BufferedWriter
- Serialización
- La API NIO.2: Path y Files
- Formatos de Intercambio: CSV y Properties
Módulo 8: Multihilo y Concurrencia
- Introducción al Multihilo
- Creación de Hilos
- Ciclo de Vida de un Hilo
- Sincronización
- Utilidades de Concurrencia
- Colecciones Concurrentes y Variables Atómicas
- Tareas Asíncronas con CompletableFuture
Módulo 9: Redes
- Introducción a las Redes
- Sockets
- ServerSocket
- DatagramSocket y DatagramPacket
- URL y HttpURLConnection
- El Cliente HTTP Moderno
Módulo 10: Temas Avanzados
- Genéricos
- Anotaciones
- Reflexión
- Características de Java 8: Streams y Optional
- Fechas y Horas con java.time
- Java 9 y Más Allá
- Memoria, Recolección de Basura y Rendimiento
Módulo 11: Frameworks y Librerías de Java
- Introducción a los Frameworks de Java
- Spring Framework
- Hibernate
- JUnit
- Maven
- Pruebas Avanzadas con Mockito
- Librerías Esenciales del Ecosistema
