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
- Operadors aritmètics i el parany de la divisió entera
- L'operador mòdul
- Operadors d'assignació composta
- Increment i decrement: pre i post
- Operadors relacionals
- Operadors lògics i avaluació de curtcircuit
- L'operador ternari
- Operadors a nivell de bit i desplaçament
- L'operador
instanceof - Precedència i associativitat
- Comparar cadenes:
==davant d'equals - La precisió de
doublei per què existeixBigDecimal - Errors Habituals i Consells
- Exercicis
- 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:
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:
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.5Compte 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 CONTINUALa 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.
- 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 diesFixa't en com / i % treballen en parella: un dona el quocient, l'altre el que sobra. És un patró que reapareixerà moltes vegades.
- 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; |
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 truncamentmentre que la forma "llarga" equivalent no compila:
És una asimetria del llenguatge que pot amagar pèrdues de precisió sense cap avís. Val la pena conèixer-la.
- 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 = 6La 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.
- 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); // trueRepeteix 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 quexsiguiboolean, perquè una assignació d'intno produeix unboolean. El compilador et protegeix.
- 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; // falseL'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, siaésfalse, el resultat ésfalsepassi el que passi ambb.bno s'avalua. - A
a || b, siaéstrue, el resultat éstruepassi el que passi ambb.bno 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'executaA 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.
- L'operador ternari
És l'únic operador de Java amb tres operands. La seva forma és:
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); // 12Aquesta 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.
- 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)); // 1000El 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 << nequival a multiplicar per 2ⁿx >> nequival 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 perdUn ú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.
- L'operador
instanceof
instanceofinstanceof comprova si un objecte és d'un tipus determinat i retorna un boolean:
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.
- 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 compilaI 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.0El 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ó.
- Comparar cadenes:
== davant d'equals
== 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)); // truePer 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 == #p1 → true. a == c compara #p1 == #h9 → false. En tots dos casos el text és el mateix.
La regla, sense excepcions
Per comparar el contingut de dos
String, fes servir sempreequals(). Mai==.
String titolIntroduit = "java eficac";
String titolCataleg = "Java Eficac";
System.out.println(titolIntroduit.equals(titolCataleg)); // false
System.out.println(titolIntroduit.equalsIgnoreCase(titolCataleg)); // truePer 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 |
- La precisió de
double i per què existeix BigDecimal
double i per què existeix BigDecimalUn ú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.6100000000000001Això 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
doubleamb==. Compara si la seva diferència és inferior a una tolerància:Math.abs(a - b) < 0.0001. - No facis servir mai
doubleper 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 exacteTé 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 / 2dona2. Divisió entera. Aplica un cast a un dels operands:(double) 5 / 2.(double)(5 / 2)continua donant2.0. El cast arriba després de la divisió. S'ha d'aplicar a un operand, no al resultat.- Comparar
Stringamb==. Funciona amb literals i falla amb dades d'entrada. Fes servirequals()sempre. - Comparar
doubleamb==. 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:
- Els dies de retard, que no poden ser mai negatius (fes servir l'operador ternari).
- El retard expressat en setmanes completes i dies solts (fes servir
/i%). - La multa base (
diesRetard * TARIFA_DIARIA). - La multa amb recàrrec si el retard supera els 30 dies (fes servir el ternari).
- La multa final, sense superar mai
MULTA_MAXIMA(fes servir el ternari). - 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:
- Defineixi quatre constants de permís amb valors 1, 2, 4 i 8: consultar, prestar, reservar i administrar.
- Compongui els permisos de tres empleats de Nexus Software: Marta Ruiz (consultar + prestar), Diego Alonso (consultar + prestar + reservar) i Nuria Vidal (els quatre).
- Mostri per a cadascun el valor enter dels seus permisos, la seva representació binària i si pot administrar.
- Afegeixi el permís de reservar a Marta Ruiz fent servir
|=i en mostri el resultat.
Solucions
Solució 1
Justificacions:
7 / 2→ 3. Tots dos operands sónint: divisió entera truncada.7 % 2→ 1. El residu de dividir 7 entre 2.7.0 / 2→ 3.5. Com que7.0és undouble, el2es 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 seguitxval 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 calcula3*4=12i després2+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.equalscompara el contingut, que sí que és idèntic.0.1 + 0.2 == 0.3→ false. La representació IEEE 754 de0.1i0.2és aproximada; la suma dona0.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) != 0són obligatoris, no opcionals.&té menys precedència que!=, així que sense ells Java intentaria avaluarpermisosMarta & (ADMINISTRAR != 0), cosa que ni tan sols compila per barrejarintiboolean. Integer.toBinaryStringno mostra els zeros a l'esquerra: el 3 apareix com a11, no com a0011. É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
- Introducció a Java
- Configuració de l'entorn de desenvolupament
- Sintaxi i estructura bàsica
- Variables i tipus de dades
- Operadors
- Entrada i sortida per consola
- El teu primer programa complet: BiblioTech
Mòdul 2: Flux de control
- Sentències condicionals
- Bucles
- Sentències switch
- Break i continue
- Depuració i traces d'execució
- Projecte: menú interactiu de BiblioTech
Mòdul 3: Programació orientada a objectes
- Introducció a la POO
- Classes i objectes
- Mètodes
- Constructors
- Herència
- Polimorfisme
- Encapsulament
- Abstracció
- La classe Object: equals, hashCode i toString
Mòdul 4: Programació orientada a objectes avançada
- Interfícies
- Classes abstractes
- Classes internes
- Classes anònimes
- Expressions lambda
- Interfícies funcionals i referències a mètodes
- Enumeracions i registres
Mòdul 5: Estructures de dades i col·leccions
- Arrays
- El framework de col·leccions
- ArrayList
- LinkedList
- HashMap
- HashSet
- Cua i Deque
- Pila
- Ordenació i cerca en col·leccions
Mòdul 6: Gestió d'excepcions
- Introducció a les excepcions
- Bloc try-catch
- Throw i throws
- Excepcions personalitzades
- Bloc finally
- Try-with-resources i AutoCloseable
- Estratègies de gestió d'errors i logging
Mòdul 7: Entrada/sortida de fitxers
- Lectura de fitxers
- Escriptura de fitxers
- Fluxos de fitxers
- BufferedReader i BufferedWriter
- Serialització
- L'API NIO.2: Path i Files
- Formats d'intercanvi: CSV i Properties
Mòdul 8: Multifil i concurrència
- Introducció al multifil
- Creació de fils
- Cicle de vida d'un fil
- Sincronització
- Utilitats de concurrència
- Col·leccions concurrents i variables atòmiques
- Tasques asíncrones amb CompletableFuture
Mòdul 9: Xarxes
- Introducció a les xarxes
- Sockets
- ServerSocket
- DatagramSocket i DatagramPacket
- URL i HttpURLConnection
- El client HTTP modern
Mòdul 10: Temes avançats
- Genèrics
- Anotacions
- Reflexió
- Característiques de Java 8: Streams i Optional
- Dates i hores amb java.time
- Java 9 i més enllà
- Memòria, recol·lecció de brossa i rendiment
Mòdul 11: Frameworks i llibreries de Java
- Introducció als frameworks de Java
- Spring Framework
- Hibernate
- JUnit
- Maven
- Proves avançades amb Mockito
- Llibreries essencials de l'ecosistema
