Fins ara els teus programes han estat monòlegs: les dades estaven escrites a foc dins del codi i la sortida es limitava a concatenar cadenes amb +, produint imports com ara 13.875 EUR que cap rebut no acceptaria. Aquesta lliçó tanca les dues mancances. Aprendràs a donar format professional a la sortida amb printf (alineacions, amplades, decimals controlats) i a llegir dades de l'usuari amb Scanner, inclòs el famós problema de barrejar nextInt() amb nextLine() que desconcerta tothom la primera vegada. En acabar, BiblioTech podrà conversar amb qui el fa servir i emetre una fitxa de llibre amb aspecte de document real.

Tot el codi d'aquesta lliçó és lineal: sense condicionals ni bucles, que arriben al mòdul 2.

Contingut

  1. La sortida estàndard: System.out
  2. print davant de println
  3. printf i els especificadors de format
  4. Amplades, alineació i altres modificadors
  5. String.format: el mateix format, dins d'una variable
  6. System.err i quan fer-lo servir
  7. Llegir dades amb Scanner
  8. Els mètodes de lectura i què retorna cadascun
  9. El problema clàssic: barrejar nextInt() amb nextLine()
  10. Tancar el Scanner
  11. Conversió de text a número: parseInt i parseDouble
  12. La fitxa de llibre de BiblioTech
  13. Errors Habituals i Consells
  14. Exercicis

  1. La sortida estàndard: System.out

System.out és un objecte que representa la sortida estàndard: normalment, la consola. Està sempre disponible sense necessitat de cap import, perquè la classe System pertany a java.lang.

Desglossem l'expressió que has anat escrivint des de la primera lliçó:

System.out.println("BiblioTech");
  • System és una classe de java.lang que dona accés a recursos del sistema.
  • out és un camp d'aquesta classe: un objecte PrintStream connectat a la consola.
  • println és un mètode d'aquell objecte.

Els tres mètodes que faràs servir:

Mètode Què fa
print(x) Escriu x sense salt de línia
println(x) Escriu x i salta de línia
printf(format, args...) Escriu amb format controlat, sense salt automàtic

  1. print davant de println

La diferència és únicament el salt de línia final, però canvia molt la composició de la sortida:

System.out.print("Titol: ");
System.out.print("Java Eficac");
System.out.println();                 // println() sense arguments nomes salta de linia
System.out.println("ISBN: 978-0000000001");

Sortida:

Titol: Java Eficac
ISBN: 978-0000000001

Tots dos mètodes accepten qualsevol tipus: String, números, boolean, char. Java converteix automàticament el valor a text.

System.out.println(2018);            // 2018
System.out.println(3.75);            // 3.75
System.out.println(true);            // true
System.out.println('J');             // J

I admeten concatenació amb +, que és el que has estat fent servir:

String titol = "Java Eficac";
int anyPublicacio = 2018;
System.out.println("Llibre: " + titol + " (" + anyPublicacio + ")");

Aquest estil funciona, però es torna illegible tan bon punt hi ha quatre o cinc variables, i no permet controlar el format: no pots dir "mostra'm aquest double amb exactament dos decimals". Per a això hi ha printf.

  1. printf i els especificadors de format

printf (de print formatted) rep una cadena de format amb marcadors que comencen per %, seguida dels valors que ompliran aquells marcadors, en ordre.

String titol = "Java Eficac";
double multa = 13.875;

System.out.printf("El llibre %s acumula una multa de %.2f EUR%n", titol, multa);

Sortida:

El llibre Java Eficac acumula una multa de 13.88 EUR

Fixa't en el que acaba de passar: 13.875 s'ha mostrat com a 13.88, arrodonit a dos decimals. Aquest control és exactament el que faltava a la lliçó anterior.

Els especificadors essencials

Especificador Tipus que espera Exemple d'ús Resultat
%s Qualsevol cosa (text) printf("%s", "Java Eficac") Java Eficac
%d Enter (int, long) printf("%d", 2018) 2018
%f Decimal (double, float) printf("%f", 3.5) 3,500000
%.2f Decimal amb 2 decimals printf("%.2f", 13.875) 13,88
%b Booleà printf("%b", true) true
%c Caràcter printf("%c", 'J') J
%n Salt de línia portable printf("Fi%n") salta de línia
%% Un símbol % literal printf("100%%") 100%

Tres punts crítics:

  • %f per defecte mostra SIS decimals. printf("%f", 3.5) imprimeix 3,500000. Gairebé sempre voldràs %.2f.
  • Fes servir %n i no \n. %n genera el salt de línia propi del sistema operatiu (\n a Unix, \r\n a Windows); \n genera sempre el d'Unix. A la consola amb prou feines es nota, però és la pràctica correcta.
  • printf no salta de línia per si sol. Si oblides el %n final, la sortida següent s'enganxarà a l'anterior.

El detall de la coma decimal

Hauràs notat que la taula mostra 13,88 amb coma. printf respecta la configuració regional del sistema: aquí el separador decimal és la coma. Si necessites un punt de manera determinista (per exemple, per generar un fitxer CSV), pots indicar la configuració regional explícitament:

import java.util.Locale;

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

És un matís que convé conèixer ara perquè no et sorprengui la sortida a la teva màquina.

Diversos valors alhora

Els valors es consumeixen en ordre, d'esquerra a dreta:

String empleat = "Marta Ruiz";
String titol = "Java Eficac";
int diesRetard = 12;
double multa = 3.0;

System.out.printf("%s te %s amb %d dies de retard: %.2f EUR%n",
        empleat, titol, diesRetard, multa);

Sortida:

Marta Ruiz te Java Eficac amb 12 dies de retard: 3,00 EUR

Si el nombre de marcadors no coincideix amb el de valors, o els tipus no encaixen, el programa falla en execució amb MissingFormatArgumentException o IllegalFormatConversionException. El cas més freqüent és fer servir %d per a un double: %d és només per a enters.

  1. Amplades, alineació i altres modificadors

Aquí és on printf deixa de ser una comoditat i es converteix en una eina: pot alinear columnes.

L'estructura completa d'un especificador és:

%[banderes][amplada][.precisió]conversió
Modificador Significat Exemple Resultat (amb | marcant les vores)
%10s Amplada 10, alineat a la dreta printf("|%10s|", "Java") | Java|
%-10s Amplada 10, alineat a l'esquerra printf("|%-10s|", "Java") |Java |
%5d Enter en amplada 5, a la dreta printf("|%5d|", 12) | 12|
%-5d Enter en amplada 5, a l'esquerra printf("|%-5d|", 12) |12 |
%05d Omple amb zeros per l'esquerra printf("%05d", 42) 00042
%8.2f Amplada 8 total, 2 decimals printf("|%8.2f|", 13.875) | 13,88|
%,d Separador de milers printf("%,d", 1250000) 1.250.000
%+d Mostra sempre el signe printf("%+d", 12) +12

Punts a recordar:

  • L'amplada és un mínim, no un màxim. Si el text és més llarg, es mostra sencer i desborda la columna.
  • El guionet activa l'alineació a l'esquerra. Sense ell, tot s'alinea a la dreta.
  • Convenció habitual: text a l'esquerra, números a la dreta. Els números alineats a la dreta permeten comparar magnituds d'un cop d'ull, perquè les unitats queden a la mateixa columna.

Aplicat a una taula del catàleg de BiblioTech:

System.out.printf("%-22s %-16s %6s %10s%n", "TITOL", "AUTOR", "ANY", "MULTA");
System.out.println("-".repeat(58));
System.out.printf("%-22s %-16s %6d %10.2f%n", "Java Eficac", "Joshua Bloch", 2018, 3.0);
System.out.printf("%-22s %-16s %6d %10.2f%n", "Patrons de Disseny", "Erich Gamma", 1994, 13.88);
System.out.printf("%-22s %-16s %6d %10.2f%n", "Refactoritzacio", "Martin Fowler", 1999, 0.0);

Sortida:

TITOL                  AUTOR                  ANY      MULTA
----------------------------------------------------------
Java Eficac            Joshua Bloch          2018       3,00
Patrons de Disseny     Erich Gamma           1994      13,88
Refactoritzacio        Martin Fowler         1999       0,00

Amb println i concatenació, aquesta alineació seria pràcticament impossible. I observa el truc de "-".repeat(58): el mètode repeat de String (Java 11+) repeteix una cadena, perfecte per a línies separadores.

  1. String.format: el mateix format, dins d'una variable

De vegades no vols imprimir el text, sinó desar-lo. String.format fa servir exactament la mateixa sintaxi que printf però retorna un String en comptes d'escriure'l:

double multa = 13.875;

String liniaRebut = String.format("Multa acumulada: %.2f EUR", multa);

System.out.println(liniaRebut);

Les dues formes següents són equivalents:

System.out.printf("Multa: %.2f EUR%n", multa);
System.out.println(String.format("Multa: %.2f EUR", multa));

String.format és l'opció quan el text formatat s'ha de reutilitzar: desar-se en un fitxer (mòdul 7), enviar-se per la xarxa (mòdul 9) o compondre un missatge que es mostrarà més tard.

  1. System.err i quan fer-lo servir

Al costat de la sortida estàndard existeix la sortida d'error estàndard, System.err, amb els mateixos mètodes:

System.out.println("Prestec registrat correctament");
System.err.println("AVIS: l'ISBN introduit no te el format esperat");

A la consola de l'IDE, System.err sol aparèixer en vermell. Però la seva raó de ser veritable és que són dos canals independents que el sistema operatiu pot redirigir per separat:

# Nomes la sortida normal va al fitxer; els errors es continuen veient a pantalla
java BiblioTechApp > rebuts.txt

# Cada canal a un fitxer diferent
java BiblioTechApp > rebuts.txt 2> errors.log

Això és el que permet que un procés automatitzat desi les dades netes en un lloc i els problemes en un altre.

Canal Es fa servir per a Exemple a BiblioTech
System.out Resultats, informació normal El rebut del préstec
System.err Errors, avisos, diagnòstics "No s'ha pogut llegir el catàleg"

Un matís tècnic: System.err no fa servir memòria intermèdia i System.out sí, per la qual cosa en barrejar-los els missatges poden aparèixer desordenats a la pantalla. No és una errada teva.

En aplicacions professionals, System.err acaba substituït per un sistema de logging amb nivells (INFO, WARN, ERROR), que s'estudia a la lliçó 06-07. Per al mòdul 1, System.err n'hi ha prou.

  1. Llegir dades amb Scanner

Scanner és la classe estàndard per llegir entrada de l'usuari. Viu a java.util, així que necessita import:

import java.util.Scanner;

public class LecturaBiblioTech {
    public static void main(String[] args) {

        // Creem un Scanner connectat a l'entrada estandard (el teclat)
        Scanner entrada = new Scanner(System.in);

        System.out.print("Nom de l'empleat: ");
        String empleat = entrada.nextLine();

        System.out.println("Benvingut a BiblioTech, " + empleat);

        entrada.close();
    }
}

Analitzem la línia clau:

Scanner entrada = new Scanner(System.in);
  • new Scanner(...) crea un objecte nou. new és l'operador de creació d'objectes; l'estudiaràs a fons al mòdul 3, però aquí ja el necessites.
  • System.in és l'entrada estàndard: el teclat, anàleg a System.out per a la sortida.
  • entrada és el nom que li donem. Podria ser sc, teclat o lector; convé que sigui descriptiu.

Un detall d'usabilitat: per demanar dades es fa servir System.out.print (sense ln), de manera que el cursor es quedi a la mateixa línia que la pregunta. És un detall petit amb molt d'impacte en la sensació de qualitat del programa.

  1. Els mètodes de lectura i què retorna cadascun

Mètode Retorna Què llegeix
nextLine() String Tota la línia, espais inclosos, fins a l'Enter
next() String Una sola paraula (fins al primer espai)
nextInt() int L'enter següent
nextDouble() double El decimal següent
nextBoolean() boolean true o false
hasNextInt() boolean El que ve és un enter? No consumeix res
hasNextLine() boolean Queda alguna línia? No consumeix res

La diferència entre nextLine() i next() importa molt a BiblioTech, on els títols i els noms porten espais:

System.out.print("Titol del llibre: ");
String titol = entrada.nextLine();       // "Java Eficac" sencer

System.out.print("Titol del llibre: ");
String paraula = entrada.next();         // nomes "Java"; "Eficac" queda pendent

Regla pràctica: per a noms i títols, fes servir sempre nextLine().

Els mètodes hasNextXxx() són especials: pregunten sense consumir. Serveixen per comprovar si el que ve és del tipus esperat abans de llegir-ho:

System.out.print("Dies transcorreguts: ");
boolean esNumeroValid = entrada.hasNextInt();
System.out.println("L'entrada es un enter? " + esNumeroValid);

La seva utilitat plena requereix condicionals (mòdul 2) per reaccionar al resultat, i bucles per insistir fins a obtenir una dada vàlida (lliçó 02-02). De moment queda't amb que existeixen i amb què fan.

  1. El problema clàssic: barrejar nextInt() amb nextLine()

Aquest és el bug que pateix absolutament tothom en aprendre Java. Observa:

Scanner entrada = new Scanner(System.in);

System.out.print("Dies transcorreguts: ");
int dies = entrada.nextInt();

System.out.print("Nom de l'empleat: ");
String empleat = entrada.nextLine();      // <- NO ESPERA: es salta la pregunta

System.out.println("[" + empleat + "]");    // imprimeix []

El programa demana els dies, i després mostra la segona pregunta i continua de llarg sense deixar-te escriure res. empleat queda buit.

Per què passa

Scanner no llegeix "per preguntes", sinó d'un flux continu de caràcters. Quan escrius 27 i prems Enter, en aquell flux hi ha:

2  7  \n

nextInt() consumeix els caràcters del número (27) i s'atura just abans del \n, deixant-lo al flux. Després, nextLine() fa el que li correspon: llegeix fins al salt de línia següent. Com que el salt és allà mateix, retorna una cadena buida immediatament, sense esperar ningú.

flowchart TD
    A["L'usuari escriu: 27 + Enter"] --> B["Flux d'entrada: '27\\n'"]
    B --> C["nextInt() consumeix '27'<br/>i DEIXA el '\\n' pendent"]
    C --> D["nextLine() troba el '\\n'<br/>i retorna cadena buida sense esperar"]
    D --> E["Sembla que el programa<br/>s'hagi saltat la pregunta"]

Les dues solucions

Solució 1: consumir el salt pendent amb un nextLine() extra.

System.out.print("Dies transcorreguts: ");
int dies = entrada.nextInt();
entrada.nextLine();                      // consumeix el \n que ha deixat nextInt()

System.out.print("Nom de l'empleat: ");
String empleat = entrada.nextLine();     // ara SI que espera

Solució 2 (la recomanada): llegir-ho tot amb nextLine() i convertir.

System.out.print("Dies transcorreguts: ");
int dies = Integer.parseInt(entrada.nextLine());   // llegeix la linia SENCERA i la converteix

System.out.print("Nom de l'empleat: ");
String empleat = entrada.nextLine();               // funciona sense sorpreses

Comparació dels dos enfocaments:

Enfocament Avantatges Inconvenients
nextInt() + nextLine() extra Menys escriptura Fàcil d'oblidar; cal recordar on posar-lo
Només nextLine() + parseInt Coherent i predictible; un mètode per a tot Una crida de conversió per dada

Aquest curs adopta la segona opció a BiblioTechApp. Quan tota la lectura passa per nextLine(), el problema desapareix per construcció i el codi és uniforme.

  1. Tancar el Scanner

Scanner és un recurs del sistema i s'ha de tancar quan ja no es necessita:

entrada.close();

Posa-ho al final de main. Si no ho fas, el compilador i l'IDE mostraran un avís de "recurs no tancat" (resource leak).

Un advertiment important: tancar un Scanner creat sobre System.in tanca també System.in. A partir d'aquell moment no pots crear un altre Scanner per llegir del teclat en el mateix programa. Per això la regla és: un sol Scanner per a tota l'aplicació, creat al principi i tancat al final.

Al mòdul 6 veuràs try-with-resources, que tanca els recursos automàticament i és la manera professional de fer-ho.

  1. Conversió de text a número: parseInt i parseDouble

Tot el que arriba de la consola és text, encara que l'usuari escrigui dígits. Per operar-hi cal convertir-ho:

String textDies = "27";
int dies = Integer.parseInt(textDies);         // 27 com a numero

String textTarifa = "0.25";
double tarifa = Double.parseDouble(textTarifa);    // 0.25 com a numero

És la raó per la qual aquestes dues línies es comporten de manera tan diferent:

System.out.println("27" + 15);                       // "2715"  -> concatenacio de text
System.out.println(Integer.parseInt("27") + 15);     // 42      -> suma de numeros

Què passa si el text no és un número

int dies = Integer.parseInt("vint-i-set");

El programa s'atura amb un error d'execució:

Exception in thread "main" java.lang.NumberFormatException: For input string: "vint-i-set"
	at java.base/java.lang.Integer.parseInt(Integer.java:668)
	at BiblioTechApp.main(BiblioTechApp.java:12)

Això és una excepció, el mecanisme de Java per assenyalar errors en execució. Capturar-les i respondre-hi de manera elegant (tornar a demanar la dada, mostrar un missatge amable) és l'objecte del mòdul 6. De moment queda't amb dues idees:

  1. Saps què significa aquell error si apareix.
  2. Els programes d'aquest mòdul assumeixen que l'usuari introdueix dades correctes. És una limitació real que reconeixerem explícitament a la lliçó següent.

Un detall útil: Double.parseDouble espera el punt com a separador decimal, no la coma. Double.parseDouble("0,25") falla; Double.parseDouble("0.25") funciona. És l'asimetria inversa a la de printf, i convé tenir-la present quan provis els teus programes.

  1. La fitxa de llibre de BiblioTech

Ajuntem-ho tot. Aquest programa demana per consola les dades d'un llibre del catàleg i n'emet una fitxa formatada:

import java.util.Scanner;

public class FitxaLlibreBiblioTech {

    public static void main(String[] args) {

        // Un unic Scanner per a tota l'aplicacio.
        Scanner entrada = new Scanner(System.in);

        // --- Constants de negoci ---
        final String NOM_EMPRESA = "Nexus Software";
        final int ANY_ACTUAL = 2026;
        final int AMPLADA_FITXA = 46;

        // --- Capcalera de benvinguda ---
        System.out.println("=".repeat(AMPLADA_FITXA));
        System.out.printf("  BiblioTech - %s%n", NOM_EMPRESA);
        System.out.println("  Alta de llibre al cataleg");
        System.out.println("=".repeat(AMPLADA_FITXA));
        System.out.println();

        // --- Recollida de dades ---
        // TOTA la lectura es fa amb nextLine() per evitar el problema
        // del salt de linia pendent que deixa nextInt().
        System.out.print("Titol del llibre   : ");
        String titol = entrada.nextLine();

        System.out.print("Autor              : ");
        String autor = entrada.nextLine();

        System.out.print("ISBN               : ");
        String isbn = entrada.nextLine();

        System.out.print("Any de publicacio  : ");
        int anyPublicacio = Integer.parseInt(entrada.nextLine());

        System.out.print("Nombre de pagines  : ");
        int pagines = Integer.parseInt(entrada.nextLine());

        System.out.print("Preu de compra     : ");
        double preu = Double.parseDouble(entrada.nextLine());

        // --- Calculs derivats ---
        int antiguitat = ANY_ACTUAL - anyPublicacio;
        double preuPerPagina = preu / pagines;       // double / int -> double
        boolean disponible = true;                   // tot llibre nou entra disponible

        // --- Fitxa formatada ---
        System.out.println();
        System.out.println("=".repeat(AMPLADA_FITXA));
        System.out.println("  FITXA DE CATALEG");
        System.out.println("=".repeat(AMPLADA_FITXA));
        System.out.printf("%-20s %s%n",       "Titol:",        titol);
        System.out.printf("%-20s %s%n",       "Autor:",        autor);
        System.out.printf("%-20s %s%n",       "ISBN:",         isbn);
        System.out.printf("%-20s %d%n",       "Publicacio:",   anyPublicacio);
        System.out.printf("%-20s %d anys%n",  "Antiguitat:",   antiguitat);
        System.out.printf("%-20s %,d%n",      "Pagines:",      pagines);
        System.out.printf("%-20s %8.2f EUR%n", "Preu:",        preu);
        System.out.printf("%-20s %8.4f EUR%n", "Preu/pagina:", preuPerPagina);
        System.out.printf("%-20s %b%n",       "Disponible:",   disponible);
        System.out.println("=".repeat(AMPLADA_FITXA));

        System.err.println("[info] Fitxa generada sense validacio de dades.");

        entrada.close();
    }
}

Exemple d'execució (el que escriu l'usuari apareix després dels dos punts):

==============================================
  BiblioTech - Nexus Software
  Alta de llibre al cataleg
==============================================

Titol del llibre   : Java Eficac
Autor              : Joshua Bloch
ISBN               : 978-0000000001
Any de publicacio  : 2018
Nombre de pagines  : 416
Preu de compra     : 45.9

==============================================
  FITXA DE CATALEG
==============================================
Titol:               Java Eficac
Autor:               Joshua Bloch
ISBN:                978-0000000001
Publicacio:          2018
Antiguitat:          8 anys
Pagines:             416
Preu:                   45,90 EUR
Preu/pagina:            0,1103 EUR
Disponible:          true
==============================================

Cinc decisions de disseny que val la pena assenyalar:

  1. Un únic Scanner, creat al principi i tancat al final.
  2. Tota la lectura amb nextLine(), amb conversió explícita. Mai no es barreja amb nextInt().
  3. System.out.print per a les preguntes, perquè el cursor es quedi a la línia.
  4. %-20s a les etiquetes, que crea una columna esquerra perfectament alineada.
  5. %8.2f per als imports: dos decimals i amplada fixa, de manera que les xifres quedin alineades per la dreta.

Errors Habituals i Consells

  • Oblidar import java.util.Scanner;. Produeix cannot find symbol: class Scanner. És l'import que més oblidaràs de tot el curs.
  • Barrejar nextInt() amb nextLine(). El programa es salta preguntes. Fes servir nextLine() per a tot i converteix amb parseInt.
  • Fer servir next() per llegir títols o noms. Es talla al primer espai: "Marta Ruiz" es converteix en "Marta".
  • Fer servir %d per a un double. Llança IllegalFormatConversionException. %d és només per a enters; per a decimals, %f.
  • Que %f mostri sis decimals. És el seu comportament per defecte; especifica sempre la precisió: %.2f.
  • Oblidar %n al final d'un printf. Totes les línies s'enganxen.
  • Crear un Scanner nou després de tancar-ne un altre sobre System.in. No funciona: System.in queda tancat. Un sol Scanner per programa.
  • Introduir 0,25 en comptes de 0.25. Double.parseDouble espera el punt i llança NumberFormatException amb la coma.
  • Consell: defineix constants per a les amplades de format (AMPLADA_FITXA) en comptes de repetir números. Canviar el disseny es converteix en canviar una línia.
  • Consell: "=".repeat(46) és molt més net que escriure 46 signes igual a mà, i no t'equivocaràs en comptar-los.
  • Consell: quan una sortida no quedi alineada, imprimeix una línia de referència amb | a les vores (printf("|%-20s|%n", text)) per veure exactament on cauen les columnes.

Exercicis

Exercici 1: Predir el comportament

Analitza aquest programa sense executar-lo. Indica què s'imprimeix, quines dades aconsegueix llegir realment i per què. Després escriu-ne dues versions corregides: una amb la tècnica del nextLine() extra i una altra llegint-ho tot amb nextLine().

import java.util.Scanner;

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

        System.out.print("Dies transcorreguts: ");
        int dies = entrada.nextInt();

        System.out.print("Nom de l'empleat: ");
        String empleat = entrada.nextLine();

        System.out.print("Titol del llibre: ");
        String titol = entrada.nextLine();

        System.out.println("Empleat: [" + empleat + "]");
        System.out.println("Titol:   [" + titol + "]");
        System.out.println("Dies:    " + dies);

        entrada.close();
    }
}

Exercici 2: Taula del catàleg alineada

Escriu la classe TaulaCataleg que imprimeixi, sense demanar res a l'usuari, la taula exacta següent amb els tres llibres de BiblioTech. Fes servir només printf i "-".repeat(...).

+------------------------+------------------+-------+------------+
| TITOL                  | AUTOR            |   ANY |       PREU |
+------------------------+------------------+-------+------------+
| Java Eficac            | Joshua Bloch     |  2018 |      45,90 |
| Patrons de Disseny     | Erich Gamma      |  1994 |      52,00 |
| Refactoritzacio        | Martin Fowler    |  1999 |      38,75 |
+------------------------+------------------+-------+------------+
| TOTAL                                     |       |     136,65 |
+------------------------+------------------+-------+------------+

Pista: defineix constants amb les amplades de cada columna i construeix la línia separadora amb repeat.

Exercici 3: Registre de devolució

Escriu la classe RegistreDevolucio que demani per consola:

  • Nom de l'empleat
  • Títol del llibre
  • ISBN
  • Dies transcorreguts des del préstec
  • Tarifa diària en euros

i mostri un rebut formatat amb: les dades introduïdes, els dies de retard (saturats a zero amb el ternari), el retard expressat en setmanes i dies, la multa amb dos decimals i una etiqueta d'estat (EN TERMINI / AMB RETARD) obtinguda amb l'operador ternari. Fes servir System.err per emetre un avís que el programa no valida les entrades. Sense if ni bucles.

Solucions

Solució 1

Comportament observat. El programa mostra les tres preguntes, però només et deixa escriure dues vegades: els dies i, després, el que acabarà sent el títol. La sortida és una cosa així:

Dies transcorreguts: 27
Nom de l'empleat: Titol del llibre: Marta Ruiz
Empleat: []
Titol:   [Marta Ruiz]
Dies:    27

Per què. nextInt() consumeix els caràcters 27 però deixa el \n de l'Enter al flux d'entrada. El primer nextLine() troba aquell salt immediatament i retorna una cadena buida sense esperar, per la qual cosa empleat queda a "" i la pregunta del nom passa de llarg. El segon nextLine() sí que espera, i el que hi escrius (encara que sigui el nom) acaba a titol. Les dades queden desplaçades una posició.

Correcció 1: nextLine() extra.

import java.util.Scanner;

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

        System.out.print("Dies transcorreguts: ");
        int dies = entrada.nextInt();
        entrada.nextLine();   // <- CLAU: consumeix el \n pendent que ha deixat nextInt()

        System.out.print("Nom de l'empleat: ");
        String empleat = entrada.nextLine();

        System.out.print("Titol del llibre: ");
        String titol = entrada.nextLine();

        System.out.println("Empleat: [" + empleat + "]");
        System.out.println("Titol:   [" + titol + "]");
        System.out.println("Dies:    " + dies);

        entrada.close();
    }
}

Correcció 2: tot amb nextLine() (recomanada).

import java.util.Scanner;

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

        System.out.print("Dies transcorreguts: ");
        // nextLine() consumeix la linia COMPLETA, salt inclos.
        // parseInt converteix despres aquell text en numero.
        int dies = Integer.parseInt(entrada.nextLine());

        System.out.print("Nom de l'empleat: ");
        String empleat = entrada.nextLine();

        System.out.print("Titol del llibre: ");
        String titol = entrada.nextLine();

        System.out.println("Empleat: [" + empleat + "]");
        System.out.println("Titol:   [" + titol + "]");
        System.out.println("Dies:    " + dies);

        entrada.close();
    }
}

La segona correcció és preferible perquè elimina la causa en comptes de pedaçar el símptoma: si totes les lectures fan servir el mateix mètode, mai no queda res pendent al flux.

Solució 2

public class TaulaCataleg {

    public static void main(String[] args) {

        // Amplades de columna centralitzades en constants:
        // canviar el disseny es canviar aquests numeros.
        final int AMPLADA_TITOL = 22;
        final int AMPLADA_AUTOR = 16;
        final int AMPLADA_ANY = 5;
        final int AMPLADA_PREU = 10;

        // La linia separadora es construeix una vegada i es reutilitza.
        // Els +2 corresponen als espais que envolten cada cella.
        String separador = "+" + "-".repeat(AMPLADA_TITOL + 2)
                         + "+" + "-".repeat(AMPLADA_AUTOR + 2)
                         + "+" + "-".repeat(AMPLADA_ANY + 2)
                         + "+" + "-".repeat(AMPLADA_PREU + 2)
                         + "+";

        // Dades del cataleg
        double preuJava = 45.90;
        double preuPatrons = 52.00;
        double preuRefactor = 38.75;
        double total = preuJava + preuPatrons + preuRefactor;

        System.out.println(separador);

        // Capcalera: text a l'esquerra (-), numeros a la dreta (sense -)
        System.out.printf("| %-22s | %-16s | %5s | %10s |%n",
                "TITOL", "AUTOR", "ANY", "PREU");

        System.out.println(separador);

        // Files de dades. %5d alinea els anys, %10.2f alinea els imports
        // per la dreta amb exactament dos decimals.
        System.out.printf("| %-22s | %-16s | %5d | %10.2f |%n",
                "Java Eficac", "Joshua Bloch", 2018, preuJava);
        System.out.printf("| %-22s | %-16s | %5d | %10.2f |%n",
                "Patrons de Disseny", "Erich Gamma", 1994, preuPatrons);
        System.out.printf("| %-22s | %-16s | %5d | %10.2f |%n",
                "Refactoritzacio", "Martin Fowler", 1999, preuRefactor);

        System.out.println(separador);

        // Fila de total: fusionem visualment les dues primeres columnes
        // fent servir una amplada igual a la suma de totes dues mes el separador intermedi.
        System.out.printf("| %-41s | %5s | %10.2f |%n", "TOTAL", "", total);

        System.out.println(separador);
    }
}

Sortida:

+------------------------+------------------+-------+------------+
| TITOL                  | AUTOR            |   ANY |       PREU |
+------------------------+------------------+-------+------------+
| Java Eficac            | Joshua Bloch     |  2018 |      45,90 |
| Patrons de Disseny     | Erich Gamma      |  1994 |      52,00 |
| Refactoritzacio        | Martin Fowler    |  1999 |      38,75 |
+------------------------+------------------+-------+------------+
| TOTAL                                     |       |     136,65 |
+------------------------+------------------+-------+------------+

El detall que cal interioritzar és el 41 de la fila del total: surt de 22 + 3 + 16 (les dues amplades més el " | " que les separava). En fusionar cel·les cal sumar les amplades i els separadors intermedis.

Solució 3

import java.util.Scanner;

public class RegistreDevolucio {

    public static void main(String[] args) {

        Scanner entrada = new Scanner(System.in);

        // === Regles de negoci de BiblioTech ===
        final String NOM_EMPRESA = "Nexus Software";
        final int DIES_PRESTEC = 15;
        final int AMPLADA = 48;

        // === Capcalera ===
        System.out.println("=".repeat(AMPLADA));
        System.out.printf("  BiblioTech - %s%n", NOM_EMPRESA);
        System.out.println("  Registre de devolucio");
        System.out.println("=".repeat(AMPLADA));
        System.out.println();

        // === Recollida de dades (tot amb nextLine) ===
        System.out.print("Empleat            : ");
        String empleat = entrada.nextLine();

        System.out.print("Titol del llibre   : ");
        String titol = entrada.nextLine();

        System.out.print("ISBN               : ");
        String isbn = entrada.nextLine();

        System.out.print("Dies transcorreguts: ");
        int diesTranscorreguts = Integer.parseInt(entrada.nextLine());

        System.out.print("Tarifa diaria (EUR): ");
        double tarifaDiaria = Double.parseDouble(entrada.nextLine());

        // === Calculs ===
        // El ternari satura a zero: una devolucio anticipada
        // no pot produir mai un retard negatiu.
        int diesRetard = diesTranscorreguts > DIES_PRESTEC
                ? diesTranscorreguts - DIES_PRESTEC
                : 0;

        int setmanes = diesRetard / 7;        // quocient enter
        int diesSolts = diesRetard % 7;       // residu

        double multa = diesRetard * tarifaDiaria;    // int * double -> double

        String estat = diesRetard > 0 ? "AMB RETARD" : "EN TERMINI";

        // === Rebut ===
        System.out.println();
        System.out.println("=".repeat(AMPLADA));
        System.out.println("  REBUT DE DEVOLUCIO");
        System.out.println("=".repeat(AMPLADA));
        System.out.printf("%-22s %s%n", "Empleat:",  empleat);
        System.out.printf("%-22s %s%n", "Llibre:",   titol);
        System.out.printf("%-22s %s%n", "ISBN:",     isbn);
        System.out.println("-".repeat(AMPLADA));
        System.out.printf("%-22s %5d dies%n", "Prestec estandard:", DIES_PRESTEC);
        System.out.printf("%-22s %5d dies%n", "Transcorreguts:",    diesTranscorreguts);
        System.out.printf("%-22s %5d dies (%d set. i %d dies)%n",
                "Retard:", diesRetard, setmanes, diesSolts);
        System.out.println("-".repeat(AMPLADA));
        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", "Estat:", estat);
        System.out.println("=".repeat(AMPLADA));

        // Avis pel canal d'error: no forma part del rebut.
        System.err.println("[avis] Aquest programa no valida les entrades introduides.");

        entrada.close();
    }
}

Exemple d'execució:

================================================
  BiblioTech - Nexus Software
  Registre de devolucio
================================================

Empleat            : Nuria Vidal
Titol del llibre   : Patrons de Disseny
ISBN               : 978-0000000002
Dies transcorreguts: 34
Tarifa diaria (EUR): 0.25

================================================
  REBUT DE DEVOLUCIO
================================================
Empleat:               Nuria Vidal
Llibre:                Patrons de Disseny
ISBN:                  978-0000000002
------------------------------------------------
Prestec estandard:        15 dies
Transcorreguts:           34 dies
Retard:                   19 dies (2 set. i 5 dies)
------------------------------------------------
Tarifa diaria:             0,25 EUR
MULTA A PAGAR:             4,75 EUR
Estat:                 AMB RETARD
================================================

Conclusió

Els teus programes ja es comuniquen en totes dues direccions. Pel costat de la sortida, domines print, println i sobretot printf amb els seus especificadors (%s, %d, %.2f, %n), les seves amplades i la seva alineació, cosa que et permet construir fitxes i taules amb aspecte professional; coneixes String.format per desar aquell text en comptes d'imprimir-lo, i saps que System.err és un canal a part per a avisos i errors. Pel costat de l'entrada, saps crear un Scanner sobre System.in, quan fer servir nextLine davant de next, per què barrejar nextInt() amb nextLine() trenca el flux i com evitar-ho llegint-ho tot com a línia i convertint amb Integer.parseInt i Double.parseDouble, i per què el Scanner s'ha de crear una vegada i tancar-se al final.

Amb això tens totes les peces del mòdul: sintaxi, variables, tipus, operadors i entrada/sortida. A la lliçó següent, El teu primer programa complet: BiblioTech, les muntaràs totes juntes a BiblioTechApp: un programa que dona la benvinguda, recull les dades d'un préstec, calcula el retard i la multa, i emet un rebut formatat. El construirem per versions successives, el compilaràs i executaràs, en comprovaràs la sortida amb dos jocs de dades de prova i analitzaràs amb honestedat què no sap fer encara i quin mòdul ho resoldrà.

Curs de Programació en Java

Mòdul 1: Introducció a Java

Mòdul 2: Flux de control

Mòdul 3: Programació orientada a objectes

Mòdul 4: Programació orientada a objectes avançada

Mòdul 5: Estructures de dades i col·leccions

Mòdul 6: Gestió d'excepcions

Mòdul 7: Entrada/sortida de fitxers

Mòdul 8: Multifil i concurrència

Mòdul 9: Xarxes

Mòdul 10: Temes avançats

Mòdul 11: Frameworks i llibreries de Java

Mòdul 12: Construcció d'aplicacions del món real

© Copyright 2026. Tots els drets reservats