Els operadors són els verbs del llenguatge: allò que converteix dades inertes en càlculs. Ja has fet servir + i - de manera intuïtiva, però Java té un catàleg ampli i uns quants paranys que atrapen tothom com a mínim una vegada: la divisió entre enters que descarta els decimals, la diferència entre i++ i ++i, la comparació de cadenes amb == que de vegades funciona i de vegades no, i la precisió traïdora de double quan hi ha diners pel mig. Aquesta lliçó els recorre tots amb calma, sempre sobre el mateix cas de BiblioTech: calcular la multa per retard d'un préstec de la biblioteca de Nexus Software.

Contingut

  1. Operadors aritmètics i el parany de la divisió entera
  2. L'operador mòdul
  3. Operadors d'assignació composta
  4. Increment i decrement: pre i post
  5. Operadors relacionals
  6. Operadors lògics i avaluació de curtcircuit
  7. L'operador ternari
  8. Operadors a nivell de bit i desplaçament
  9. L'operador instanceof
  10. Precedència i associativitat
  11. Comparar cadenes: == davant d'equals
  12. La precisió de double i per què existeix BigDecimal
  13. Errors Habituals i Consells
  14. Exercicis

  1. Operadors aritmètics i el parany de la divisió entera

Els cinc operadors aritmètics bàsics:

Operador Nom Exemple Resultat
+ Suma 12 + 3 15
- Resta 27 - 15 12
* Multiplicació 12 * 2 24
/ Divisió 12 / 5 2 (atenció!)
% Mòdul (residu) 12 % 5 2

El parany del 5 / 2

És l'error número u de qui comença en Java:

int total = 5;
int parts = 2;
System.out.println(total / parts);      // imprimeix 2, no 2.5

La regla és la següent: si tots dos operands són enters, la divisió és entera i el resultat es trunca cap a zero, descartant els decimals. Java no arrodoneix, talla.

I això no s'arregla desant el resultat en un double:

double resultat = total / parts;
System.out.println(resultat);           // 2.0, no 2.5

Per què? Perquè l'operació total / parts s'avalua primer, entre enters, donant 2; només després aquell 2 es converteix a double. La conversió arriba massa tard.

Les tres solucions correctes:

// 1. Cast d'un dels operands ABANS de dividir
double resultat = (double) total / parts;          // 2.5

// 2. Declarar les variables com a double des del principi
double totalD = 5;
double partsD = 2;
double resultat2 = totalD / partsD;                // 2.5

// 3. Multiplicar per 1.0 (truc menys llegible, pero es veu en codi real)
double resultat3 = total * 1.0 / parts;            // 2.5

Compte amb l'ordre al cast: (double)(total / parts) no funciona. Els parèntesis fan que es divideixi primer entre enters i es converteixi després: dona 2.0. El cast s'ha d'aplicar a un dels operands.

Divisió per zero

Depèn del tipus, i la diferència és important:

int a = 5 / 0;         // ArithmeticException: / by zero -> el programa s'atura
double b = 5.0 / 0;    // Infinity  -> el programa CONTINUA
double c = 0.0 / 0;    // NaN (Not a Number) -> el programa CONTINUA

La divisió entera per zero llança un error d'execució. La divisió decimal produeix valors especials (Infinity, NaN) que es propaguen silenciosament per tots els càlculs posteriors, cosa que pot ser pitjor: acabes mostrant NaN EUR en un rebut sense haver vist cap error.

Aplicat a BiblioTech

final int DIES_PRESTEC = 15;

int diesTranscorreguts = 27;
int diesRetard = diesTranscorreguts - DIES_PRESTEC;    // 12 dies de retard

final double TARIFA_DIARIA = 0.25;
double multa = diesRetard * TARIFA_DIARIA;             // 12 * 0.25 = 3.0

System.out.println("Dies de retard: " + diesRetard);
System.out.println("Multa: " + multa + " EUR");

Aquí diesRetard és int i TARIFA_DIARIA és double: Java promociona l'enter a decimal abans de multiplicar i el resultat és correcte. La promoció automàtica ens salva; la divisió és l'única que cal vigilar.

  1. L'operador mòdul

% retorna el residu d'una divisió entera. És molt més útil del que sembla a primer cop d'ull.

System.out.println(17 % 5);     // 2  (17 = 5*3 + 2)
System.out.println(12 % 4);     // 0  (divisio exacta)
System.out.println(3 % 5);      // 3  (3 = 5*0 + 3)
System.out.println(-7 % 3);     // -1 (en Java el signe el dona el DIVIDEND)
System.out.println(7.5 % 2);    // 1.5 (tambe funciona amb decimals)

Usos habituals:

Objectiu Expressió Explicació
És parell? n % 2 == 0 Residu zero en dividir entre 2
És múltiple de 7? n % 7 == 0 Generalització de l'anterior
Últim dígit n % 10 Residu de dividir entre 10
Cicla en un rang 0..n−1 i % n Mai no se surt del rang
Descompondre temps minuts % 60 Minuts que sobren de les hores completes

Aplicat a BiblioTech, per expressar el retard en setmanes i dies solts:

int diesRetard = 27;

int setmanesRetard = diesRetard / 7;     // divisio entera: 3 setmanes
int diesSolts = diesRetard % 7;          // residu: 6 dies

System.out.println("Retard: " + setmanesRetard + " setmanes i " + diesSolts + " dies");
// Retard: 3 setmanes i 6 dies

Fixa't en com / i % treballen en parella: un dona el quocient, l'altre el que sobra. És un patró que reapareixerà moltes vegades.

  1. Operadors d'assignació composta

Són dreceres per operar sobre una variable i desar-ne el resultat en ella mateixa:

Operador Equivalent a Exemple
+= x = x + y multa += 0.25;
-= x = x - y diesRetard -= 3;
*= x = x * y multa *= 2;
/= x = x / y total /= 4;
%= x = x % y comptador %= 7;
double multa = 3.0;
multa += 1.50;      // multa val ara 4.5
multa *= 2;         // multa val ara 9.0

Hi ha un detall poc conegut: l'assignació composta inclou un cast implícit. Això compila:

int diesRetard = 12;
diesRetard += 0.75;                  // compila: equival a diesRetard = (int)(diesRetard + 0.75)
System.out.println(diesRetard);      // 12  <- el 0.75 es perd en el truncament

mentre que la forma "llarga" equivalent no compila:

diesRetard = diesRetard + 0.75;      // ERROR: incompatible types: possible lossy conversion

És una asimetria del llenguatge que pot amagar pèrdues de precisió sense cap avís. Val la pena conèixer-la.

  1. Increment i decrement: pre i post

++ suma 1 i -- en resta 1. L'interessant és que es poden escriure abans o després de la variable, i això canvia el valor de l'expressió:

  • Post-increment x++: fa servir el valor actual, i després incrementa.
  • Pre-increment ++x: incrementa primer, i després fa servir el valor nou.
int a = 5;
int resultatA = a++;      // resultatA rep 5, i despres a passa a valer 6
System.out.println("a = " + a + ", resultatA = " + resultatA);
// a = 6, resultatA = 5

int b = 5;
int resultatB = ++b;      // b passa a valer 6 primer, i resultatB rep 6
System.out.println("b = " + b + ", resultatB = " + resultatB);
// b = 6, resultatB = 6

La taula resumeix la diferència partint sempre de x = 5:

Expressió Valor de l'expressió Valor final de x
x++ 5 6
++x 6 6
x-- 5 4
--x 4 4

Quan importa de debò: si el ++ està sol a la seva pròpia línia (comptador++;), pre i post són idèntics i tant se val quin facis servir. La diferència només es manifesta quan el valor de l'expressió es fa servir per a alguna cosa, com ara una assignació, un índex o un argument.

Consell pràctic: no escriguis expressions que depenguin d'aquesta subtilesa. Codi com ara int x = i++ + ++i; és un endevinalla, no un programa. Quan importi, separa-ho en dues línies clares.

  1. Operadors relacionals

Comparen dos valors i sempre produeixen un boolean:

Operador Significat Exemple Resultat
== Igual a diesRetard == 0 false si hi ha retard
!= Diferent de diesRetard != 0 true si hi ha retard
> Més gran que 27 > 15 true
< Més petit que 12 < 15 true
>= Més gran o igual 15 >= 15 true
<= Més petit o igual 20 <= 15 false
final int DIES_PRESTEC = 15;
int diesTranscorreguts = 27;

boolean hiHaRetard = diesTranscorreguts > DIES_PRESTEC;
System.out.println("Hi ha retard: " + hiHaRetard);    // true

Repeteix la idea de la lliçó anterior: una comparació és una expressió que val true o false per ella mateixa. No necessites cap if per obtenir-la; l'if (mòdul 2) serveix per decidir què fer-ne.

Dos advertiments:

  • == compara valors en primitius, però referències en objectes. És el tema de l'apartat 11.
  • L'error tipogràfic més car de la programació és escriure = (assignació) on volies == (comparació). En Java, afortunadament, if (x = 5) no compila llevat que x sigui boolean, perquè una assignació d'int no produeix un boolean. El compilador et protegeix.

  1. Operadors lògics i avaluació de curtcircuit

Combinen valors booleans:

Operador Nom Resultat
&& I lògic (AND) true només si tots dos són true
|| O lògic (OR) true si almenys un és true
! Negació (NOT) Inverteix el valor

Taules de veritat:

a b a && b a || b !a
true true true true false
true false false true false
false true false true true
false false false false true

Aplicat a BiblioTech:

boolean disponible = false;
int diesRetard = 12;
boolean esEmpleatActiu = true;

boolean estaPrestatIAmbRetard = !disponible && diesRetard > 0;   // true
boolean necessitaAvis = diesRetard > 0 || !disponible;           // true
boolean potReservar = disponible && esEmpleatActiu;              // false

L'avaluació de curtcircuit

Aquesta és la part important. && i || són peresosos: deixen d'avaluar tan bon punt el resultat ja està decidit.

  • A a && b, si a és false, el resultat és false passi el que passi amb b. b no s'avalua.
  • A a || b, si a és true, el resultat és true passi el que passi amb b. b no s'avalua.
flowchart TD
    A["avaluar a"] --> B{"quin operador?"}
    B -- "AND lògic" --> C{"a és false?"}
    C -- "Sí" --> D["resultat false<br/><b>b NO s'avalua</b>"]
    C -- "No" --> E["avaluar b<br/>resultat = b"]
    B -- "OR lògic" --> F{"a és true?"}
    F -- "Sí" --> G["resultat true<br/><b>b NO s'avalua</b>"]
    F -- "No" --> H["avaluar b<br/>resultat = b"]

Per què importa? Perquè permet protegir operacions perilloses col·locant la comprovació de seguretat primer:

String empleat = null;

// Aixo FALLA amb NullPointerException: s'intenta cridar length() sobre null
boolean valid = empleat.length() > 0 && empleat != null;

// Aixo FUNCIONA: si empleat es null, la primera condicio es false
// i la segona ni tan sols s'avalua
boolean valid = empleat != null && empleat.length() > 0;

L'ordre de les condicions no és estètic: és funcional. Recorda-ho, perquè és un patró que faràs servir constantment a partir del mòdul 6.

Les versions sense curtcircuit: & i |

Existeixen & i | aplicats a booleans, que sempre avaluen totes dues bandes:

boolean r1 = false && metodeQueImprimeix();   // el metode NO s'executa
boolean r2 = false &  metodeQueImprimeix();   // el metode SI que s'executa

A la pràctica es fan servir && i || gairebé sempre. Recorda que & i | tenen a més un altre significat completament diferent sobre números, que és l'apartat següent.

  1. L'operador ternari

És l'únic operador de Java amb tres operands. La seva forma és:

condicio ? valorSiCert : valorSiFals

Es llegeix: "si la condició és certa, el resultat és el primer; si no, el segon".

int diesTranscorreguts = 27;
final int DIES_PRESTEC = 15;

int diesRetard = diesTranscorreguts > DIES_PRESTEC
        ? diesTranscorreguts - DIES_PRESTEC
        : 0;

System.out.println("Dies de retard: " + diesRetard);   // 12

Aquesta línia fa una cosa molt valuosa per a nosaltres: evita que el retard sigui negatiu. Si el llibre es retorna als 10 dies, 10 - 15 donaria -5, i una multa negativa seria un absurd (la biblioteca pagant a l'empleat!). El ternari ens permet corregir-ho sense fer servir if, que encara no hem vist.

L'ús més habitual és adaptar un text:

double multa = diesRetard * 0.25;
String estat = diesRetard > 0 ? "AMB RETARD" : "EN TERMINI";
String moneda = multa == 1.0 ? "euro" : "euros";

System.out.println("Estat del prestec: " + estat);
System.out.println("Import: " + multa + " " + moneda);

Es poden imbricar, però la llegibilitat es degrada molt de pressa:

// Llegible al limit
String categoria = diesRetard == 0 ? "PUNTUAL"
                 : diesRetard <= 7 ? "LLEU"
                 : "GREU";

Regla pràctica: el ternari és excel·lent per triar un valor; tan bon punt vulguis executar diverses accions diferents, necessites un if, que arriba a la lliçó 02-01.

  1. Operadors a nivell de bit i desplaçament

Aquests operadors treballen sobre la representació binària dels nombres enters. Al principi semblen esotèrics, però apareixen en permisos, banderes, criptografia, processament d'imatges i protocols de xarxa, i a les entrevistes tècniques.

Recordatori: un int són 32 bits. El número 12 és 00000000 00000000 00000000 00001100, i el 10 és ...00001010.

Els operadors bit a bit

Operador Nom Regla per bit 12 op 10 Resultat
& AND 1 només si tots dos són 1 1100 & 1010 1000 = 8
| OR 1 si algun és 1 1100 | 1010 1110 = 14
^ XOR 1 si són diferents 1100 ^ 1010 0110 = 6
~ NOT Inverteix tots els bits ~12 -13
int a = 12;   // 1100
int b = 10;   // 1010

System.out.println(a & b);    // 8   -> 1000
System.out.println(a | b);    // 14  -> 1110
System.out.println(a ^ b);    // 6   -> 0110
System.out.println(~a);       // -13

// Veure la representacio binaria es la millor manera d'entendre-ho
System.out.println(Integer.toBinaryString(a));       // 1100
System.out.println(Integer.toBinaryString(a & b));   // 1000

El resultat de ~12 = -13 sorprèn. La raó és que Java representa els enters negatius en complement a dos, on ~x equival sempre a -x - 1.

Els operadors de desplaçament

Operador Nom Què fa Exemple Resultat
<< Desplaçament a l'esquerra Mou els bits a l'esquerra, omple amb zeros 12 << 2 48
>> Desplaçament a la dreta amb signe Mou a la dreta, conserva el signe 12 >> 2 3
>>> Desplaçament a la dreta sense signe Mou a la dreta, omple amb zeros -12 >>> 28 15

La relació amb l'aritmètica és directa i molt fàcil de recordar:

  • x << n equival a multiplicar per 2ⁿ
  • x >> n equival a dividir entre 2ⁿ (divisió entera)
System.out.println(12 << 1);    // 24   (12 * 2)
System.out.println(12 << 2);    // 48   (12 * 4)
System.out.println(12 >> 1);    // 6    (12 / 2)
System.out.println(12 >> 2);    // 3    (12 / 4)
System.out.println(-12 >> 2);   // -3   conserva el signe
System.out.println(-12 >>> 2);  // 1073741821  omple amb zeros: el signe es perd

Un ús realista: banderes de permisos

El cas on aquests operadors brillen de debò és empaquetar diverses opcions booleanes en un sol enter. A BiblioTech podríem codificar els permisos d'un empleat sobre el catàleg:

// Cada permis ocupa un bit diferent
final int PERMIS_CONSULTAR = 1;     // 0001
final int PERMIS_PRESTAR   = 2;     // 0010
final int PERMIS_RESERVAR  = 4;     // 0100
final int PERMIS_ADMINISTRAR = 8;   // 1000

// Marta Ruiz pot consultar, prendre prestat i reservar
int permisosMarta = PERMIS_CONSULTAR | PERMIS_PRESTAR | PERMIS_RESERVAR;   // 0111 = 7

// Comprovar un permis concret: AND amb la bandera
boolean potReservar    = (permisosMarta & PERMIS_RESERVAR) != 0;      // true
boolean potAdministrar = (permisosMarta & PERMIS_ADMINISTRAR) != 0;   // false

System.out.println("Permisos de Marta Ruiz: " + permisosMarta);
System.out.println("Pot reservar: " + potReservar);
System.out.println("Pot administrar: " + potAdministrar);

Quatre booleans en un sol int, amb comprovacions instantànies. És el mecanisme que fan servir per sota els permisos de fitxers d'Unix i moltíssimes API.

  1. L'operador instanceof

instanceof comprova si un objecte és d'un tipus determinat i retorna un boolean:

String titol = "Java Eficac";
boolean esText = titol instanceof String;   // true

Aquí només s'esmenta per completar el catàleg d'operadors. La seva utilitat veritable apareix quan hi ha jerarquies de tipus i polimorfisme, és a dir, a partir del mòdul 3. Des de Java 16 admet a més la coincidència de patrons (if (obj instanceof String s)), que s'estudia al mòdul 10.

  1. Precedència i associativitat

Quan una expressió combina diversos operadors, Java aplica un ordre fix. De més a menys prioritat:

Nivell Operadors Associativitat
1 () [] . Esquerra a dreta
2 ++ -- (postfixos) Esquerra a dreta
3 ++ -- (prefixos), + - (unaris), !, ~, casts Dreta a esquerra
4 * / % Esquerra a dreta
5 + - Esquerra a dreta
6 << >> >>> Esquerra a dreta
7 < <= > >= instanceof Esquerra a dreta
8 == != Esquerra a dreta
9 & Esquerra a dreta
10 ^ Esquerra a dreta
11 | Esquerra a dreta
12 && Esquerra a dreta
13 || Esquerra a dreta
14 ? : (ternari) Dreta a esquerra
15 = += -= *= /= %= Dreta a esquerra

L'associativitat decideix què passa quan hi ha operadors del mateix nivell: 10 - 4 - 3 s'agrupa com (10 - 4) - 3 = 3, no com 10 - (4 - 3) = 9.

Exemples on la precedència canvia el resultat:

System.out.println(2 + 3 * 4);        // 14, no 20: * va abans que +
System.out.println((2 + 3) * 4);      // 20

System.out.println(10 - 4 - 3);       // 3: associatiu per l'esquerra

// Parany classic: & te MENYS prioritat que !=
// (a & b) != 0  es el correcte; a & b != 0 s'agrupa com a & (b != 0) i ni compila

I un cas real de BiblioTech on falten parèntesis:

int diesTranscorreguts = 27;
final int DIES_PRESTEC = 15;
final double TARIFA_DIARIA = 0.25;

// MALAMENT: s'avalua (diesTranscorreguts) - (DIES_PRESTEC * TARIFA_DIARIA)
double multa = diesTranscorreguts - DIES_PRESTEC * TARIFA_DIARIA;   // 23.25, absurd

// BE: els parentesis expressen la intencio
double multa = (diesTranscorreguts - DIES_PRESTEC) * TARIFA_DIARIA; // 3.0

El consell definitiu: no memoritzis la taula. Fes servir parèntesis. Ningú no ha perdut mai una revisió de codi per posar parèntesis de més, i sí moltes hores per confiar en la precedència. Els parèntesis documenten la teva intenció.

  1. Comparar cadenes: == davant d'equals

És l'error clàssic per excel·lència de Java, i el pitjor és que de vegades funciona, cosa que endarrereix el diagnòstic durant setmanes.

String a = "Java Eficac";
String b = "Java Eficac";
System.out.println(a == b);         // true  (!)

String c = new String("Java Eficac");
System.out.println(a == c);         // false (!)
System.out.println(a.equals(c));    // true

Per què passa: el pool de cadenes

Recorda de la lliçó anterior que una variable String no conté el text, sinó una referència a un objecte del monticle. I == sobre referències compara si apunten al mateix objecte, no si el contingut és igual.

La JVM manté un pool de cadenes (string pool): una zona especial on desa els literals. Quan compila String a = "Java Eficac"; i després String b = "Java Eficac";, veu que el literal és idèntic i fa que totes dues variables apuntin al mateix objecte del pool. Per això a == b dona true. És una optimització de memòria, no una promesa del llenguatge.

Però new String("Java Eficac") força la creació d'un objecte nou, fora del pool. Ara hi ha dos objectes amb el mateix text en adreces diferents, i == dona false.

flowchart LR
    subgraph Pila
        A["a"]
        B["b"]
        C["c"]
    end
    subgraph Pool["String pool"]
        P["#p1<br/>'Java Eficac'"]
    end
    subgraph Heap["Monticle"]
        H["#h9<br/>'Java Eficac'"]
    end
    A --> P
    B --> P
    C --> H

a == b compara #p1 == #p1true. a == c compara #p1 == #h9false. En tots dos casos el text és el mateix.

La regla, sense excepcions

Per comparar el contingut de dos String, fes servir sempre equals(). Mai ==.

String titolIntroduit = "java eficac";
String titolCataleg = "Java Eficac";

System.out.println(titolIntroduit.equals(titolCataleg));             // false
System.out.println(titolIntroduit.equalsIgnoreCase(titolCataleg));   // true

Per què el perill és real: les cadenes que arriben d'un Scanner, d'un fitxer o de la xarxa no són al pool. Així que la teva comparació amb == funcionarà perfectament a les proves amb literals i fallarà amb dades reals.

Una tècnica defensiva habitual és posar el literal a l'esquerra, perquè un literal no és mai null:

// Si isbn fos null, aixo llancaria NullPointerException
if (isbn.equals("978-0000000001")) { ... }

// Aixo es segur fins i tot si isbn es null
if ("978-0000000001".equals(isbn)) { ... }

Resum en taula:

Comparació Amb primitius Amb objectes (String)
== Compara valors. Correcte Compara referències. Gairebé sempre incorrecte
equals() No existeix (els primitius no són objectes) Compara contingut. Correcte

  1. La precisió de double i per què existeix BigDecimal

Un últim avís, particularment rellevant perquè BiblioTech manega diners:

System.out.println(0.1 + 0.2);              // 0.30000000000000004
System.out.println(0.1 + 0.2 == 0.3);       // false
System.out.println(1.03 - 0.42);            // 0.6100000000000001

Això no és una errada de Java: passa en Python, JavaScript, C i pràcticament a tots els llenguatges. La causa és que float i double segueixen l'estàndard IEEE 754, que representa els números en binari. De la mateixa manera que en decimal no pots escriure 1/3 exactament (0,3333…), en binari no pots escriure 0,1 exactament. El valor emmagatzemat és una aproximació boníssima, però aproximació.

Conseqüències pràctiques:

  • No comparis mai dos double amb ==. Compara si la seva diferència és inferior a una tolerància: Math.abs(a - b) < 0.0001.
  • No facis servir mai double per a diners en un sistema real. Els errors s'acumulen al llarg de milers d'operacions i acaben descompassant la comptabilitat.

La solució de Java és la classe BigDecimal, que representa decimals amb precisió exacta i permet controlar l'arrodoniment:

import java.math.BigDecimal;

BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b));      // 0.3 exacte

Té el seu cost: és més verbós (no es pot fer servir +, cal cridar add()) i més lent. S'estudia al mòdul 10; en aquest curs continuarem fent servir double per a les multes de BiblioTech perquè el focus és aprendre el llenguatge, però quedes advertit: en un sistema de facturació real, BigDecimal.

Errors Habituals i Consells

  • 5 / 2 dona 2. Divisió entera. Aplica un cast a un dels operands: (double) 5 / 2.
  • (double)(5 / 2) continua donant 2.0. El cast arriba després de la divisió. S'ha d'aplicar a un operand, no al resultat.
  • Comparar String amb ==. Funciona amb literals i falla amb dades d'entrada. Fes servir equals() sempre.
  • Comparar double amb ==. Fes servir una tolerància.
  • Oblidar parèntesis a (a - b) * c. La multiplicació té més prioritat que la resta.
  • Confondre & amb &&. Sobre booleans, && evita avaluar el segon operand; & no. Sobre enters, & és una operació bit a bit completament diferent.
  • Ordenar malament les condicions protegides. x != null && x.length() > 0, mai a l'inrevés.
  • Escriure expressions acrobàtiques amb ++. Si t'has d'aturar a pensar, refactoritza-ho en dues línies.
  • Consell: fes servir parèntesis sempre que l'expressió tingui més de dos operadors. No hi ha penalització de rendiment i el guany en claredat és enorme.
  • Consell: Integer.toBinaryString(n) és la millor eina per entendre els operadors de bits. Prova-ho a JShell.
  • Consell: el ternari és perfecte per saturar valors a zero (x > 0 ? x : 0), un patró que faràs servir molt a BiblioTech perquè els dies de retard no siguin mai negatius.

Exercicis

Exercici 1: Predir la sortida

Sense executar el codi, escriu la sortida de cada línia i justifica'n el perquè.

public class ProvaOperadors {
    public static void main(String[] args) {
        System.out.println(7 / 2);
        System.out.println(7 % 2);
        System.out.println(7.0 / 2);
        System.out.println((double)(7 / 2));

        int x = 5;
        System.out.println(x++);
        System.out.println(x);
        System.out.println(++x);

        System.out.println(2 + 3 * 4);
        System.out.println(10 - 4 - 3);

        String a = "Refactoritzacio";
        String b = "Refactoritzacio";
        String c = new String("Refactoritzacio");
        System.out.println(a == b);
        System.out.println(a == c);
        System.out.println(a.equals(c));

        System.out.println(0.1 + 0.2 == 0.3);
        System.out.println(12 & 10);
        System.out.println(12 << 2);
    }
}

Exercici 2: Calculadora de multa de BiblioTech

Escriu la classe CalculadoraMulta que, a partir de les constants i dades següents, calculi i mostri la liquidació d'un préstec:

  • Constants: DIES_PRESTEC = 15, TARIFA_DIARIA = 0.25, MULTA_MAXIMA = 20.0, RECARREC_GREU = 1.5 (multiplicador que s'aplica si el retard supera els 30 dies).
  • Dades del préstec: empleat "Diego Alonso", llibre "Refactoritzacio", diesTranscorreguts = 52.

Ha de calcular i mostrar:

  1. Els dies de retard, que no poden ser mai negatius (fes servir l'operador ternari).
  2. El retard expressat en setmanes completes i dies solts (fes servir / i %).
  3. La multa base (diesRetard * TARIFA_DIARIA).
  4. La multa amb recàrrec si el retard supera els 30 dies (fes servir el ternari).
  5. La multa final, sense superar mai MULTA_MAXIMA (fes servir el ternari).
  6. Una etiqueta de gravetat: "EN TERMINI", "LLEU" (1-7 dies) o "GREU" (més de 7), amb ternaris imbricats.

Tot sense fer servir if.

Exercici 3: Banderes de permisos

Fent servir els operadors a nivell de bit, escriu la classe PermisosBiblioTech que:

  1. Defineixi quatre constants de permís amb valors 1, 2, 4 i 8: consultar, prestar, reservar i administrar.
  2. Compongui els permisos de tres empleats de Nexus Software: Marta Ruiz (consultar + prestar), Diego Alonso (consultar + prestar + reservar) i Nuria Vidal (els quatre).
  3. Mostri per a cadascun el valor enter dels seus permisos, la seva representació binària i si pot administrar.
  4. Afegeixi el permís de reservar a Marta Ruiz fent servir |= i en mostri el resultat.

Solucions

Solució 1

3
1
3.5
3.0
5
6
7
14
3
true
false
true
false
8
48

Justificacions:

  • 7 / 2 → 3. Tots dos operands són int: divisió entera truncada.
  • 7 % 2 → 1. El residu de dividir 7 entre 2.
  • 7.0 / 2 → 3.5. Com que 7.0 és un double, el 2 es promociona i la divisió és decimal.
  • (double)(7 / 2) → 3.0. Els parèntesis forcen la divisió entera primer (3) i el cast arriba després.
  • x++ → 5, i tot seguit x val 6. El post-increment retorna el valor anterior.
  • x → 6. Confirma l'increment previ.
  • ++x → 7. El pre-increment incrementa primer (7) i retorna el valor nou.
  • 2 + 3 * 4 → 14. * té més precedència que +: es calcula 3*4=12 i després 2+12.
  • 10 - 4 - 3 → 3. Associativitat per l'esquerra: (10-4)-3.
  • a == b → true. Tots dos són literals idèntics, així que la JVM els comparteix al pool de cadenes i les dues referències apunten al mateix objecte.
  • a == c → false. new String(...) crea un objecte diferent fora del pool; les referències difereixen encara que el text sigui igual.
  • a.equals(c) → true. equals compara el contingut, que sí que és idèntic.
  • 0.1 + 0.2 == 0.3 → false. La representació IEEE 754 de 0.1 i 0.2 és aproximada; la suma dona 0.30000000000000004.
  • 12 & 10 → 8. En binari, 1100 & 1010 = 1000, que és 8.
  • 12 << 2 → 48. Desplaçar 2 bits a l'esquerra equival a multiplicar per 2² = 4.

Solució 2

public class CalculadoraMulta {

    public static void main(String[] args) {

        // === Regles de negoci de BiblioTech (Nexus Software) ===
        final int DIES_PRESTEC = 15;         // dies de prestec sense recarrec
        final double TARIFA_DIARIA = 0.25;   // euros per dia de retard
        final double MULTA_MAXIMA = 20.0;    // sostre absolut per prestec
        final double RECARREC_GREU = 1.5;    // multiplicador si el retard passa de 30 dies
        final int LLINDAR_RECARREC = 30;
        final int LLINDAR_LLEU = 7;

        // === Dades del prestec ===
        String empleat = "Diego Alonso";
        String titol = "Refactoritzacio";
        String isbn = "978-0000000003";
        int diesTranscorreguts = 52;

        // 1. Dies de retard, saturat a zero amb l'operador ternari.
        //    Si es retornes abans d'hora, la resta seria negativa i
        //    produiria una multa negativa, que no te cap sentit.
        int diesRetard = diesTranscorreguts > DIES_PRESTEC
                ? diesTranscorreguts - DIES_PRESTEC
                : 0;                                          // 52 - 15 = 37

        // 2. Descomposicio en setmanes i dies: / dona el quocient, % el residu.
        int setmanes = diesRetard / 7;                        // 5
        int diesSolts = diesRetard % 7;                       // 2

        // 3. Multa base. int * double -> Java promociona l'int a double.
        double multaBase = diesRetard * TARIFA_DIARIA;        // 37 * 0.25 = 9.25

        // 4. Recarrec per retard greu, de nou amb el ternari.
        double multaAmbRecarrec = diesRetard > LLINDAR_RECARREC
                ? multaBase * RECARREC_GREU
                : multaBase;                                  // 9.25 * 1.5 = 13.875

        // 5. Sostre: la multa no supera mai MULTA_MAXIMA.
        double multaFinal = multaAmbRecarrec > MULTA_MAXIMA
                ? MULTA_MAXIMA
                : multaAmbRecarrec;                           // 13.875 < 20 -> 13.875

        // 6. Etiqueta de gravetat amb ternaris imbricats.
        //    S'avaluen en cadena, de dalt a baix, com una escala.
        String gravetat = diesRetard == 0 ? "EN TERMINI"
                        : diesRetard <= LLINDAR_LLEU ? "LLEU"
                        : "GREU";

        // === Sortida ===
        System.out.println("=== BiblioTech - Liquidacio de prestec ===");
        System.out.println("Empleat:         " + empleat);
        System.out.println("Llibre:          " + titol);
        System.out.println("ISBN:            " + isbn);
        System.out.println("Dies transcorreguts: " + diesTranscorreguts
                + " (prestec estandard: " + DIES_PRESTEC + " dies)");
        System.out.println();
        System.out.println("Dies de retard:  " + diesRetard
                + " (" + setmanes + " setmanes i " + diesSolts + " dies)");
        System.out.println("Gravetat:        " + gravetat);
        System.out.println("Multa base:      " + multaBase + " EUR");
        System.out.println("Amb recarrec:    " + multaAmbRecarrec + " EUR");
        System.out.println("MULTA FINAL:     " + multaFinal + " EUR");
    }
}

Sortida:

=== BiblioTech - Liquidacio de prestec ===
Empleat:         Diego Alonso
Llibre:          Refactoritzacio
ISBN:            978-0000000003
Dies transcorreguts: 52 (prestec estandard: 15 dies)

Dies de retard:  37 (5 setmanes i 2 dies)
Gravetat:        GREU
Multa base:      9.25 EUR
Amb recarrec:    13.875 EUR
MULTA FINAL:     13.875 EUR

Observa el 13.875: tres decimals per a un import en euros. És exactament el tipus de resultat que en un sistema real exigiria BigDecimal o, com a mínim, un formatatge a dos decimals. Això últim ho resoldrem a la lliçó següent amb printf.

Solució 3

public class PermisosBiblioTech {

    public static void main(String[] args) {

        // 1. Cada permis ocupa UN bit diferent: potencies de 2.
        //    Aixi cap no interfereix amb els altres en combinar-los.
        final int CONSULTAR    = 1;   // 0001
        final int PRESTAR      = 2;   // 0010
        final int RESERVAR     = 4;   // 0100
        final int ADMINISTRAR  = 8;   // 1000

        // 2. Es combinen amb OR: activa els bits de tots dos operands.
        int permisosMarta = CONSULTAR | PRESTAR;                          // 0011 = 3
        int permisosDiego = CONSULTAR | PRESTAR | RESERVAR;               // 0111 = 7
        int permisosNuria = CONSULTAR | PRESTAR | RESERVAR | ADMINISTRAR; // 1111 = 15

        // 3. Es comprova amb AND: si el bit esta actiu, el resultat no es zero.
        boolean martaAdmin = (permisosMarta & ADMINISTRAR) != 0;   // false
        boolean diegoAdmin = (permisosDiego & ADMINISTRAR) != 0;   // false
        boolean nuriaAdmin = (permisosNuria & ADMINISTRAR) != 0;   // true

        System.out.println("=== BiblioTech - Permisos de cataleg ===");
        System.out.println("Marta Ruiz:   " + permisosMarta
                + " (" + Integer.toBinaryString(permisosMarta) + ")"
                + " admin=" + martaAdmin);
        System.out.println("Diego Alonso: " + permisosDiego
                + " (" + Integer.toBinaryString(permisosDiego) + ")"
                + " admin=" + diegoAdmin);
        System.out.println("Nuria Vidal:  " + permisosNuria
                + " (" + Integer.toBinaryString(permisosNuria) + ")"
                + " admin=" + nuriaAdmin);

        // 4. Afegir un permis amb |= : encen aquell bit sense tocar els altres.
        permisosMarta |= RESERVAR;                                 // 0011 | 0100 = 0111 = 7

        System.out.println();
        System.out.println("Despres de concedir RESERVAR a Marta Ruiz:");
        System.out.println("Marta Ruiz:   " + permisosMarta
                + " (" + Integer.toBinaryString(permisosMarta) + ")"
                + " pot reservar=" + ((permisosMarta & RESERVAR) != 0));
    }
}

Sortida:

=== BiblioTech - Permisos de cataleg ===
Marta Ruiz:   3 (11) admin=false
Diego Alonso: 7 (111) admin=false
Nuria Vidal:  15 (1111) admin=true

Despres de concedir RESERVAR a Marta Ruiz:
Marta Ruiz:   7 (111) pot reservar=true

Dos detalls que mereixen atenció:

  • Els parèntesis a (permisosMarta & ADMINISTRAR) != 0 són obligatoris, no opcionals. & té menys precedència que !=, així que sense ells Java intentaria avaluar permisosMarta & (ADMINISTRAR != 0), cosa que ni tan sols compila per barrejar int i boolean.
  • Integer.toBinaryString no mostra els zeros a l'esquerra: el 3 apareix com a 11, no com a 0011. És normal; representa el mateix valor.

Conclusió

Ja domines el catàleg complet d'operadors de Java: l'aritmètica amb el seu parany de la divisió entera, el mòdul i la seva parella natural amb /, l'assignació composta amb el seu cast implícit ocult, l'increment pre i post i quan la diferència importa, les comparacions que produeixen booleans, els operadors lògics amb l'avaluació de curtcircuit que et permet protegir expressions perilloses, el ternari per triar valors sense if, les operacions a nivell de bit aplicades a banderes de permisos, la precedència i el consell de fer servir parèntesis, la diferència crítica entre == i equals explicada pel pool de cadenes, i les limitacions de precisió de double que un dia et portaran a BigDecimal. Amb això, BiblioTech ja sap calcular una multa completa: dies de retard saturats a zero, recàrrec per gravetat i sostre màxim.

A la lliçó següent, Entrada i sortida per consola, resoldrem el que ha quedat lleig en aquests exemples: deixarem d'escriure imports com ara 13.875 i aprendrem a formatar la sortida amb printf (%.2f, amplades, alineació), i sobretot deixarem de tenir les dades escrites a foc dins del codi per demanar-les a l'usuari amb Scanner. És l'últim ingredient abans de construir la primera versió completa de BiblioTechApp.

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