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

  1. La salida estándar: System.out
  2. print frente a println
  3. printf y los especificadores de formato
  4. Anchos, alineación y otros modificadores
  5. String.format: el mismo formato, en una variable
  6. System.err y cuándo usarlo
  7. Leer datos con Scanner
  8. Los métodos de lectura y qué devuelve cada uno
  9. El problema clásico: mezclar nextInt() con nextLine()
  10. Cerrar el Scanner
  11. Conversión de texto a número: parseInt y parseDouble
  12. La ficha de libro de BiblioTech
  13. Errores Comunes y Consejos
  14. Ejercicios

  1. La salida estándar: System.out

System.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:

System.out.println("BiblioTech");
  • System es una clase de java.lang que da acceso a recursos del sistema.
  • out es un campo de esa clase: un objeto PrintStream conectado a la consola.
  • println es 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

  1. print frente a println

La 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:

Titulo: Java Efectivo
ISBN: 978-0000000001

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');             // J

Y 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.

  1. printf y los especificadores de formato

printf (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:

El libro Java Efectivo acumula una multa de 13.88 EUR

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:

  • %f por defecto muestra SEIS decimales. printf("%f", 3.5) imprime 3,500000. Casi siempre querrás %.2f.
  • Usa %n y no \n. %n genera el salto de línea propio del sistema operativo (\n en Unix, \r\n en Windows); \n siempre genera el de Unix. En consola apenas se nota, pero es la práctica correcta.
  • printf no salta de línea por sí solo. Si olvidas el %n final, 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:

import java.util.Locale;

System.out.printf(Locale.US, "%.2f%n", 13.875);   // 13.88 con punto

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:

Marta Ruiz tiene Java Efectivo con 12 dias de retraso: 3,00 EUR

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.

  1. 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:

%[banderas][ancho][.precisión]conversión
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.

  1. String.format: el mismo formato, en una variable

A 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.

  1. System.err y cuándo usarlo

Junto 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.log

Esto 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.

  1. Leer datos con Scanner

Scanner 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:

Scanner entrada = new Scanner(System.in);
  • new Scanner(...) crea un objeto nuevo. new es el operador de creación de objetos; lo estudiarás a fondo en el módulo 3, pero aquí ya lo necesitas.
  • System.in es la entrada estándar: el teclado, análogo a System.out para la salida.
  • entrada es el nombre que le damos. Podría ser sc, teclado o lector; 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.

  1. 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 pendiente

Regla 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.

  1. El problema clásico: mezclar 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:

2  7  \n

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 espera

Solució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 sorpresas

Comparació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.

  1. Cerrar el Scanner

Scanner es un recurso del sistema y debe cerrarse cuando ya no se necesita:

entrada.close();

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.

  1. Conversión de texto a número: parseInt y parseDouble

Todo 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 numero

Es 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 numeros

Qué pasa si el texto no es un número

int dias = Integer.parseInt("veintisiete");

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:

  1. Sabes qué significa ese error si aparece.
  2. 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.

  1. 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:

  1. Un único Scanner, creado al principio y cerrado al final.
  2. Toda la lectura con nextLine(), con conversión explícita. Nunca se mezcla con nextInt().
  3. System.out.print para las preguntas, para que el cursor se quede en la línea.
  4. %-20s en las etiquetas, que crea una columna izquierda perfectamente alineada.
  5. %8.2f para 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;. Produce cannot find symbol: class Scanner. Es el import que más olvidarás en todo el curso.
  • Mezclar nextInt() con nextLine(). El programa se salta preguntas. Usa nextLine() para todo y convierte con parseInt.
  • Usar next() para leer títulos o nombres. Se corta en el primer espacio: "Marta Ruiz" se convierte en "Marta".
  • Usar %d para un double. Lanza IllegalFormatConversionException. %d es solo para enteros; para decimales, %f.
  • Que %f muestre seis decimales. Es su comportamiento por defecto; especifica siempre la precisión: %.2f.
  • Olvidar %n al final de un printf. Todas las líneas se pegan.
  • Crear un Scanner nuevo después de cerrar otro sobre System.in. No funciona: System.in queda cerrado. Un solo Scanner por programa.
  • Introducir 0,25 en lugar de 0.25. Double.parseDouble espera el punto y lanza NumberFormatException con 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

Módulo 2: Flujo de Control

Módulo 3: Programación Orientada a Objetos

Módulo 4: Programación Orientada a Objetos Avanzada

Módulo 5: Estructuras de Datos y Colecciones

Módulo 6: Manejo de Excepciones

Módulo 7: Entrada/Salida de Archivos

Módulo 8: Multihilo y Concurrencia

Módulo 9: Redes

Módulo 10: Temas Avanzados

Módulo 11: Frameworks y Librerías de Java

Módulo 12: Construcción de Aplicaciones del Mundo Real

© Copyright 2026. Todos los derechos reservados