Les teves classes ja modelen el domini, protegeixen els seus invariants i exposen una API mínima. I tanmateix, si imprimeixes un llibre obtens això: com.nexussoftware.bibliotech.domini.Llibre@1b6d3586. I si compares dos exemplars de "Java Eficaç" amb el mateix ISBN, Java et diu que no són iguals. Tots dos comportaments vénen del mateix lloc: la classe Object, arrel de tota la jerarquia de Java, que proporciona implementacions per defecte d'una sèrie de mètodes universals. Aquestes implementacions funcionen, però són deliberadament conservadores: toString no sap què és important de la teva classe i equals no sap què significa "igual" al teu domini. Només tu ho pots dir. Aquesta lliçó t'ensenya a fer-ho bé, amb el contracte complet de cada mètode, perquè equals i hashCode mal escrits produeixen errors que no es manifesten fins que els teus objectes entren en una col·lecció, i llavors la fallada sembla màgia negra. I en acabar, tancaràs el mòdul complint la promesa que vas deixar al final del mòdul 2.
Contingut
Object, l'arrel de tot- Recorregut pels mètodes d'
Object toString: què és aquest@1b6d3586- Sobreescriure
toStringcorrectament equals: igualtat de referència enfront d'igualtat lògica- El contracte d'
equals - Implementar
equalspas a pas - L'error clàssic:
equals(Llibre)en lloc d'equals(Object) hashCode: el contracte i per què va sempre ambequals- Implementar
hashCodeambObjects.hash - Què es trenca si no sobreescrius
hashCode getClass()enfront d'instanceofaequalsObjects.equalsiObjects.requireNonNull- Tancament del mòdul: la refactorització final de BiblioTech
- Errors Habituals i Consells
- Exercicis
Object, l'arrel de tot
Object, l'arrel de totCom vas veure a 03-05, tota classe Java hereta de java.lang.Object, directament o indirectament. Si no escrius extends, el compilador ho posa per tu.
classDiagram
Object <|-- Material
Object <|-- Empleat
Object <|-- Prestec
Object <|-- String
Material <|-- Llibre
Material <|-- Revista
Material <|-- Dvd
class Object {
+toString() String
+equals(Object) boolean
+hashCode() int
+getClass() Class
#clone() Object
}
Això té dues conseqüències pràctiques de les quals ja t'has beneficiat sense saber-ho:
- Tot objecte té els mètodes d'
Object. Pots cridartoString()sobre qualsevol cosa. Objectserveix com a tipus comú universal. Un paràmetreObjectaccepta qualsevol objecte, i per aixòSystem.out.println(Object)funciona amb el que hi posis. És el polimorfisme de 03-06 portat al límit.
- Recorregut pels mètodes d'
Object
Object| Mètode | Què fa per defecte | Se sobreescriu? |
|---|---|---|
toString() |
Retorna NomClasse@hashHex |
Gairebé sempre |
equals(Object) |
Compara referències (==) |
Quan la igualtat sigui lògica |
hashCode() |
Retorna un enter derivat de la identitat | Sempre que sobreescriguis equals |
getClass() |
Retorna la classe real de l'objecte | No (és final) |
clone() |
Còpia superficial; requereix Cloneable |
Desaconsellat: fes servir un constructor de còpia (03-04) |
finalize() |
S'invocava abans de recollir | Obsolet des de Java 9; no el facis servir mai |
wait(), notify(), notifyAll() |
Coordinació entre fils | No; s'estudien al mòdul 8 |
Dos aclariments sobre les files problemàtiques:
clone() està desaconsellat. El seu disseny obliga a implementar la interfície marcadora Cloneable, produeix còpies superficials per defecte i té una interacció confusa amb final i l'herència. L'alternativa és la que ja coneixes: un constructor de còpia.
finalize() no s'ha de fer servir mai. Està marcat com a obsolet (deprecated) des de Java 9 i eliminat de l'ús pràctic: no hi ha cap garantia que s'executi, ni de quan, ni tan sols que s'executi alguna vegada. Per alliberar recursos existeix try-with-resources, que s'estudia a la lliçó 06-06.
toString: què és aquest @1b6d3586
toString: què és aquest @1b6d3586Quan concatenes o imprimeixes un objecte, Java crida el seu toString():
Llibre llibre = new Llibre("Java Eficac", "Joshua Bloch", "978-0000000001", 2018);
System.out.println(llibre);La implementació per defecte a Object és, literalment:
Descompost:
| Part | Què és |
|---|---|
com.nexussoftware.bibliotech.domini.Llibre |
El nom complet de la classe, amb paquet |
@ |
Un separador literal |
1b6d3586 |
El hashCode() de l'objecte, en hexadecimal |
I aquí convé desfer un malentès molt estès: aquest número no és l'adreça de memòria. És el valor retornat per hashCode(), que en la implementació per defecte es deriva de la identitat de l'objecte, però que la JVM pot calcular de diverses formes i que no canvia encara que el recol·lector de brossa mogui l'objecte de lloc. És un identificador, no una posició.
Com a identificador funciona; com a informació, és inútil. Per això toString se sobreescriu gairebé sempre.
- Sobreescriure
toString correctament
toString correctamentUn bon toString és breu, informatiu i sense efectes secundaris. Ha de contenir les dades que identifiquen l'objecte per a un humà.
@Override
public String toString() {
return String.format("Llibre[isbn=%s, titol=%s, autor=%s, any=%d, disponible=%b]",
getIsbn(), getTitol(), autor, anyPublicacio, estaDisponible());
}System.out.println(llibre);
// Llibre[isbn=978-0000000001, titol=Java Eficac, autor=Joshua Bloch, any=2018, disponible=true]Regles pràctiques:
| Regla | Motiu |
|---|---|
| Inclou els camps identificatius | És el que buscaràs en un log |
| Mantén-lo en una línia | Els logs es llegeixen i es filtren per línies |
| No el facis servir com a format de dades | Si algú analitza el teu toString, no el podràs canviar mai |
| No incloguis dades sensibles | Contrasenyes i tokens acaben als logs |
| No provoquis efectes secundaris | Es crida en moments inesperats, fins i tot des del depurador |
| Compte amb la recursió | Si Prestec.toString() imprimeix l'Empleat i aquest imprimeix els seus préstecs, tens un StackOverflowError |
Un toString útil per a Prestec, que delega en el que ja sap:
@Override
public String toString() {
return String.format("Prestec[%s, material=%s, empleat=%s, transcorreguts=%d, multa=%.2f, %s]",
referencia, getTitolMaterial(), getNomEmpleat(),
diesTranscorreguts, calcularMulta(), classificarGravetat());
}Aquest canvi, per si sol, millora radicalment la depuració: la finestra de variables de l'IDE i qualsevol System.out.println passen a mostrar informació llegible en lloc de @1b6d3586.
equals: igualtat de referència enfront d'igualtat lògica
equals: igualtat de referència enfront d'igualtat lògicaReprenem el == del mòdul 1, ara amb objectes propis. Hi ha dues nocions d'igualtat:
| Noció | Operador | Pregunta que respon |
|---|---|---|
| Igualtat de referència (identitat) | == |
Són el mateix objecte en memòria? |
| Igualtat lògica (equivalència) | equals |
Representen el mateix segons el domini? |
Amb la implementació per defecte d'Object, totes dues són idèntiques, perquè Object.equals és exactament això:
D'aquí el comportament que sorprèn:
Llibre a = new Llibre("Java Eficac", "Joshua Bloch", "978-0000000001", 2018);
Llibre b = new Llibre("Java Eficac", "Joshua Bloch", "978-0000000001", 2018);
System.out.println(a == b); // false: dos objectes diferents
System.out.println(a.equals(b)); // false: equals per defecte es ==És correcte? Depèn del que signifiqui "igual" a BiblioTech. I aquesta és una decisió de negoci, no tècnica:
- Si
Llibrerepresenta una obra bibliogràfica, dos objectes amb el mateix ISBN són el mateix llibre. → cal sobreescriureequals. - Si
Llibrerepresenta un exemplar físic, dos exemplars de la mateixa obra són coses diferents encara que comparteixin ISBN. → la igualtat per identitat és correcta.
A BiblioTech adoptem la primera interpretació: dos llibres són iguals si tenen el mateix ISBN. És la definició que fa servir el sector editorial mateix, i encaixa amb el nostre vocabulari del domini.
Recorda a més la lliçó del mòdul 1 amb String: "Java Eficac" == altraCadena pot donar false encara que el text sigui idèntic, i per això sempre comparaves amb equals. Ara ja saps exactament per què: String sí que sobreescriu equals per comparar el contingut.
- El contracte d'
equals
equalsequals no és un mètode qualsevol: té un contracte que tota la biblioteca estàndard de Java assumeix que compleixes. Si el trenques, les col·leccions del mòdul 5 es comportaran de manera impredictible i la culpa no serà seva.
Per a referències no nul·les x, y, z:
| Propietat | Què exigeix | Exemple de violació |
|---|---|---|
| Reflexiva | x.equals(x) és true |
Comparar un camp double que valgui NaN |
| Simètrica | Si x.equals(y) llavors y.equals(x) |
Un Llibre que es consideri igual a un String amb el seu ISBN |
| Transitiva | Si x.equals(y) i y.equals(z), llavors x.equals(z) |
Comparacions que ignoren camps en una subclasse |
| Consistent | Crides repetides retornen el mateix si no canvia l'estat | Fer servir un camp mutable o aleatori |
Davant de null |
x.equals(null) és false, mai no llança cap error |
Oblidar la comprovació de nul·litat |
La violació més freqüent en codi real és la simetria, i sol aparèixer així:
// MALAMENT: asimetric
@Override
public boolean equals(Object obj) {
if (obj instanceof String) {
return getIsbn().equals(obj); // un Llibre "igual" a un String
}
// ...
}String.equals mai no considerarà igual un Llibre, així que la relació no és simètrica i qualsevol col·lecció que en depengui fallarà de manera aparentment aleatòria.
Consell derivat del contracte: compara només amb objectes del teu propi tipus.
- Implementar
equals pas a pas
equals pas a pasL'estructura canònica té cinc passos, i convé escriure-la sempre igual:
@Override
public boolean equals(Object obj) {
// 1. Drecera per identitat: el mateix objecte sempre es igual a si mateix.
// Es rapid i garanteix la propietat reflexiva.
if (this == obj) {
return true;
}
// 2. Comprovacio de tipus i de nullitat alhora.
// instanceof retorna false si obj es null, aixi que cobreix el contracte
// de "x.equals(null) es false" sense una comprovacio a part.
if (!(obj instanceof Llibre)) {
return false;
}
// 3. Conversio segura al tipus propi.
Llibre altre = (Llibre) obj;
// 4. Comparacio dels camps significatius.
// A BiblioTech, la identitat d'un llibre es el seu ISBN.
return getIsbn().equals(altre.getIsbn());
}Amb l'instanceof amb patró de Java 16 (lliçó 03-06), els passos 2 i 3 es fonen en un:
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Llibre altre)) return false;
return getIsbn().equals(altre.getIsbn());
}Comprovació:
Llibre a = new Llibre("Java Eficac", "Joshua Bloch", "978-0000000001", 2018);
Llibre b = new Llibre("Java Eficac", "J. Bloch", "978-0000000001", 2017); // dades diferents
Llibre c = new Llibre("Refactoritzacio", "Martin Fowler", "978-0000000003", 2018);
System.out.println(a.equals(a)); // true (reflexiva)
System.out.println(a.equals(b)); // true (mateix ISBN)
System.out.println(b.equals(a)); // true (simetrica)
System.out.println(a.equals(c)); // false (ISBN diferent)
System.out.println(a.equals(null)); // false (contracte amb null)
System.out.println(a.equals("978-0000000001")); // false (tipus diferent)
System.out.println(a == b); // false (continuen sent dos objectes)Fixa't en el cas a.equals(b): els títols i anys són diferents, però l'ISBN coincideix, així que són el mateix llibre. Això és exactament el que vam decidir a l'apartat 5: la igualtat la defineix el domini, no la coincidència de tots els camps.
Sobre quins camps fer servir, tres criteris:
- Només camps significatius per a la identitat. Un camp derivat o d'estat transitori (com
disponible) no hi ha d'entrar. - Preferiblement camps
final. Si un camp fet servir aequalscanvia, l'objecte canvia d'identitat, amb les conseqüències de l'apartat 11. - Compte amb els tipus: per a
floatidouble, fes servirFloat.compare/Double.compareen lloc de==, perquèNaN != NaNtrencaria la reflexivitat, i0.0i-0.0són==però es distingeixen en el bit.
- L'error clàssic:
equals(Llibre) en lloc d'equals(Object)
equals(Llibre) en lloc d'equals(Object)Aquest és l'error que cal veure un cop per no repetir-lo mai. Fixa't en la signatura:
public class Llibre extends Material {
// MALAMENT: aixo NO sobreescriu Object.equals, ho SOBRECARREGA
public boolean equals(Llibre altre) {
return getIsbn().equals(altre.getIsbn());
}
}Compila sense cap queixa. I funciona... de vegades:
Llibre a = new Llibre("Java Eficac", "Joshua Bloch", "978-0000000001", 2018);
Llibre b = new Llibre("Java Eficac", "Joshua Bloch", "978-0000000001", 2018);
System.out.println(a.equals(b)); // true <-- fa servir EL TEU metode
Object oa = a;
Object ob = b;
System.out.println(oa.equals(ob)); // false <-- fa servir Object.equalsEl mateix parell d'objectes, dos resultats oposats. Per què?
Perquè equals(Llibre) i equals(Object) tenen signatures diferents: són una sobrecàrrega, no una sobreescriptura (taula de 03-05). I la sobrecàrrega es resol en compilació, segons el tipus declarat (03-06). Amb a i b declarats com a Llibre, el compilador tria el teu mètode; amb oa i ob declarats com a Object, tria el d'Object, que compara referències.
I el pitjor és que totes les col·leccions del mòdul 5 criden equals(Object), perquè internament treballen amb referències genèriques. Així que el teu mètode no es faria servir mai on més falta fa, i l'error apareixeria com "el HashSet té llibres duplicats" sense cap pista sobre la causa.
La defensa és l'anotació que ja coneixes:
Un segon escrivint @Override enfront d'una tarda depurant. Aquesta és, probablement, la raó més contundent de totes les que hem donat per posar-la sempre.
hashCode: el contracte i per què va sempre amb equals
hashCode: el contracte i per què va sempre amb equalshashCode() retorna un enter que resumeix el contingut de l'objecte. El seu ús és a les estructures basades en taules hash —HashMap i HashSet, mòdul 5—, que l'empren per localitzar objectes ràpidament sense comparar un a un.
El seu contracte té tres clàusules:
| Clàusula | Enunciat |
|---|---|
| 1. Consistència | Si l'objecte no canvia, hashCode() retorna sempre el mateix valor durant l'execució |
2. Coherència amb equals |
Si x.equals(y) és true, llavors x.hashCode() == y.hashCode() obligatòriament |
| 3. Recíproca no exigida | Si x.hashCode() == y.hashCode(), x i y no han de ser iguals per força (a això se'n diu col·lisió, i és normal) |
La clàusula 2 és la que imposa la regla d'or:
Sempre que sobreescriguis
equals, sobreescriuhashCode. Sense excepcions.
La lògica és inevitable: si dos objectes són "iguals" però produeixen codis hash diferents, una taula hash els col·locarà en cubells diferents i mai no els compararà entre si, de manera que no descobrirà mai que són iguals.
Bona notícia: la clàusula 3 significa que un hashCode no ha de ser únic. Hi pot haver col·lisions; l'estructura les resol comparant amb equals dins del cubell. Un hashCode que retornés sempre 0 seria correcte (compleix el contracte) però inútil, perquè degradaria el HashMap a una cerca lineal.
- Implementar
hashCode amb Objects.hash
hashCode amb Objects.hashLa forma moderna i recomanada és una línia, fent servir la classe utilitària java.util.Objects:
Regla imprescindible: hashCode ha de fer servir exactament els mateixos camps que equals. Si equals compara l'ISBN, hashCode es calcula amb l'ISBN. Si n'afegeixes un camp a l'un, afegeix-l'hi a l'altre. És la causa número u d'incoherències.
Amb diversos camps:
Objects.hash(...) és un varargs (03-03) que combina els codis hash dels seus arguments amb un algorisme estàndard. Internament fa el mateix que la fórmula clàssica, que convé conèixer perquè la veuràs en codi antic i en entrevistes:
@Override
public int hashCode() {
int resultat = 17; // primer inicial
resultat = 31 * resultat + isbn.hashCode(); // 31: primer senar
resultat = 31 * resultat + Integer.hashCode(any);
return resultat;
}El 31 es fa servir perquè és primer senar i perquè 31 * x s'optimitza a (x << 5) - x. No cal que l'escriguis a mà: Objects.hash és més llegible i prou bo.
Una precaució de rendiment: Objects.hash crea un array intern per als varargs, així que en classes amb milions d'instàncies en bucles crítics es pot notar. Per a tota la resta —inclòs BiblioTech—, és l'opció correcta.
- Què es trenca si no sobreescrius
hashCode
hashCodeAquest apartat justifica tot l'anterior. Suposa que sobreescrius equals a Llibre però oblides hashCode:
Llibre a = new Llibre("Java Eficac", "Joshua Bloch", "978-0000000001", 2018);
Llibre b = new Llibre("Java Eficac", "Joshua Bloch", "978-0000000001", 2018);
System.out.println(a.equals(b)); // true
System.out.println(a.hashCode()); // 460141958
System.out.println(b.hashCode()); // 1163157884 <-- diferentsSón "iguals" i tenen codis hash diferents: la clàusula 2 està trencada. Les conseqüències, que patiràs al mòdul 5:
// Avancament del modul 5, nomes per veure el simptoma
Set<Llibre> cataleg = new HashSet<>();
cataleg.add(a);
System.out.println(cataleg.contains(b)); // false, encara que a.equals(b) sigui true!
cataleg.add(b);
System.out.println(cataleg.size()); // 2, amb el mateix ISBN dues vegadesUn HashSet que admet duplicats. Un HashMap on guardes amb una clau i no pots recuperar amb una altra d'idèntica. I cap error, cap excepció, cap pista: simplement, resultats incorrectes.
flowchart TD
A["cataleg.contains(b)"] --> B["Calcula b.hashCode()"]
B --> C{"Hi ha alguna cosa
en aquest cubell?"}
C -- "no (cubell buit)" --> D["Retorna false
sense cridar mai equals"]
C -- "si" --> E["Compara amb equals
dins del cubell"]
Aquí hi ha la clau del diagrama: si el hashCode no coincideix, equals ni tan sols s'arriba a cridar. Per això l'error és tan desconcertant: el teu equals és perfecte i tot i així el resultat és incorrecte.
Regla final, en una frase: equals i hashCode s'escriuen junts, es modifiquen junts i es revisen junts.
getClass() enfront d'instanceof a equals
getClass() enfront d'instanceof a equalsExisteix una decisió de disseny al pas 2 de l'equals, i mereix conèixer-se perquè té implicacions reals amb l'herència.
Variant amb instanceof (la que hem fet servir):
Variant amb getClass():
La diferència apareix amb subclasses. Imagina un LlibreSignat extends Llibre:
| Comparació | Amb instanceof |
Amb getClass() |
|---|---|---|
llibre.equals(llibreSignat) amb el mateix ISBN |
true |
false |
llibreSignat.equals(llibre) amb el mateix ISBN |
Depèn de si la subclasse sobreescriu equals |
false |
| Simetria garantida? | No, si la subclasse afegeix camps a la comparació | Sí, sempre |
| Permet igualtat entre classes de la jerarquia? | Sí | No |
El problema clàssic d'instanceof: si LlibreSignat sobreescriu equals afegint el signant a la comparació, llavors llibre.equals(llibreSignat) donaria true (només mira l'ISBN) mentre que llibreSignat.equals(llibre) donaria false (a més mira el signant). Asimetria, i contracte trencat.
| Tria... | Quan... |
|---|---|
getClass() |
Vols simetria garantida i que cada classe concreta sigui el seu propi tipus d'igualtat |
instanceof |
Vols que les subclasses siguin intercanviables, i et compromets que cap no afegeixi camps a equals |
| Cap preocupació | La classe és final (llavors són equivalents) |
Decisió per a BiblioTech. Com que Llibre pot tenir subclasses i volem simetria a prova de tot, la implementació definitiva farà servir getClass(). I l'alternativa més neta continua sent la de 03-07: declarar final la classe quan no estigui dissenyada per ser estesa, amb la qual cosa el dilema desapareix.
Nota sobre
record. Unrecord(lliçó 04-07) genera automàticamentequals,hashCodeitoStringa partir de tots els seus components, amb el contracte correctament implementat. És la millor raó per fer-los servir en objectes de dades immutables. Però fixa't en el matís: l'equalsgenerat fa servir tots els components, mentre que el nostre fa servir només l'ISBN. Si la teva noció d'igualtat no és "tots els camps coincideixen", elrecordno et serveix tal com és.
Objects.equals i Objects.requireNonNull
Objects.equals i Objects.requireNonNullLa classe java.util.Objects aporta dues utilitats que veuràs constantment.
Objects.equals(a, b) compara dues referències tractant null correctament, sense que hagis d'escriure la guarda:
// Sense Objects.equals: cal protegir-se
return (autor == null) ? (altre.autor == null) : autor.equals(altre.autor);
// Amb Objects.equals: una linia, i null-safe
return Objects.equals(autor, altre.autor);La seva implementació és exactament (a == b) || (a != null && a.equals(b)). Fes-la servir sempre dins d'equals quan un camp pugui ser nul.
Objects.requireNonNull(objecte, missatge) verifica que una referència no sigui nul·la i falla immediatament si ho és. El seu valor és a fallar aviat i amb un missatge clar, al constructor, en lloc de deixar que un null viatgi pel sistema i peti tres capes més enllà:
public Prestec(Material material, Empleat empleat, int diaPrestec, int diesTranscorreguts) {
this.material = Objects.requireNonNull(material, "El prestec necessita un material");
this.empleat = Objects.requireNonNull(empleat, "El prestec necessita un empleat");
// ...
}Si algú passa null, el programa s'atura allà mateix amb:
Exception in thread "main" java.lang.NullPointerException: El prestec necessita un material
at com.nexussoftware.bibliotech.domini.Prestec.<init>(Prestec.java:42)Compara-ho amb el marcador "Material desconegut" que fèiem servir: aquell emmascarava l'error i permetia que el sistema continués amb dades falses. requireNonNull és la primera forma correcta de rebutjar un argument invàlid que veus al curs, i és un avançament del mòdul 6, on aprendràs a llançar excepcions deliberadament. A partir d'ara, a les classes de domini, és preferible als avisos per consola.
- Tancament del mòdul: la refactorització final de BiblioTech
Ha arribat el moment de complir la promesa que va tancar el mòdul 2. Recorda el problema:
// BiblioTechApp 2.0: tres variables soltes que descriuen UNA sola cosa
int retardMesGran = 0;
String empleatRetardMesGran = "-";
String llibreRetardMesGran = "-";
// ...i la seva actualitzacio, que cal recordar de fer sencera:
if (diesRetard > retardMesGran) {
retardMesGran = diesRetard;
empleatRetardMesGran = empleat;
llibreRetardMesGran = titol;
}Els tres defectes que assenyalàvem llavors: res no garanteix que les tres s'actualitzin juntes; el compilador no pot ajudar; i si demà vols afegir la multa o la referència, cal afegir una quarta variable i una altra línia que recordar.
Amb el que has après al mòdul, les tres variables es converteixen en una:
// BiblioTechApp 3.0: un unic objecte coherent
Prestec prestecRetardMesGran = null;
// ...i la seva actualitzacio, atomica per construccio:
if (prestecRetardMesGran == null
|| prestec.calcularDiesRetard() > prestecRetardMesGran.calcularDiesRetard()) {
prestecRetardMesGran = prestec;
}Una sola assignació. És impossible deixar l'estat a mitges, perquè no hi ha parts per actualitzar per separat: o canvia l'objecte sencer, o no canvia res. I mostrar-lo és una línia, gràcies a toString:
if (prestecRetardMesGran != null) {
System.out.println(" Retard mes gran: " + prestecRetardMesGran);
} else {
System.out.println(" Retard mes gran: (cap de registrat)");
}Retard mes gran: Prestec[PR-0003, material=Refactoritzacio, empleat=Nuria Vidal, transcorreguts=95, multa=20,00, GREU]
Compara les dues versions:
| Aspecte | Mòdul 2 | Ara |
|---|---|---|
| Variables implicades | 3 soltes | 1 objecte |
| Línies a l'actualització | 4, totes obligatòries | 1 |
| Es pot descoordinar | Sí | Impossible |
| Afegir la multa a l'informe | Quarta variable + quarta línia | Ja hi és dins |
| Mostrar-ho | 3 printf |
1 println |
| Comparar dos màxims | Comparar tres variables | equals |
Estat final de BiblioTech després del mòdul 3
Les classes del domini, amb els seus tres mètodes d'Object implementats:
// Llibre.java (extracte: els metodes d'Object)
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Llibre altre = (Llibre) obj;
return getIsbn().equals(altre.getIsbn()); // igualtat per ISBN
}
@Override
public int hashCode() {
return Objects.hash(getIsbn()); // MATEIX camp que equals
}
@Override
public String toString() {
return String.format("Llibre[isbn=%s, titol=%s, autor=%s, any=%d, disponible=%b]",
getIsbn(), getTitol(), autor, anyPublicacio, estaDisponible());
}// Empleat.java (extracte)
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Empleat altre = (Empleat) obj;
return identificador.equals(altre.identificador); // identitat = EMP-XXX
}
@Override
public int hashCode() { return Objects.hash(identificador); }
@Override
public String toString() {
return String.format("Empleat[%s, %s, prestecs=%d]",
identificador, nom, prestecsAcumulats);
}// Prestec.java (extracte)
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Prestec altre = (Prestec) obj;
return referencia.equals(altre.referencia); // identitat = PR-XXXX
}
@Override
public int hashCode() { return Objects.hash(referencia); }
@Override
public String toString() {
return String.format("Prestec[%s, material=%s, empleat=%s, transcorreguts=%d, multa=%.2f, %s]",
referencia, getTitolMaterial(), getNomEmpleat(),
diesTranscorreguts, calcularMulta(), classificarGravetat());
}I un main que ja no calcula res:
package com.nexussoftware.bibliotech;
import com.nexussoftware.bibliotech.domini.*;
import com.nexussoftware.bibliotech.presentacio.RebutConsola;
public class BiblioTechApp {
public static void main(String[] args) {
RebutConsola rebut = new RebutConsola();
System.out.println("=== BiblioTech 3.2 - Fi del modul 3 ===\n");
Llibre javaEficac = new Llibre("Java Eficac", "Joshua Bloch", "978-0000000001", 2018);
Llibre patrons = new Llibre("Patrons de Disseny", "Erich Gamma", "978-0000000002", 1994);
Llibre refactor = new Llibre("Refactoritzacio", "Martin Fowler", "978-0000000003", 2018);
Revista revista = new Revista("Java Magazine", "REV-2024-42", 42, "Bimestral");
Dvd dvd = new Dvd("Curs de Spring", "DVD-0007", 240);
Empleat marta = new Empleat("Marta Ruiz", "EMP-001");
Empleat diego = new Empleat("Diego Alonso", "EMP-002");
Empleat nuria = new Empleat("Nuria Vidal", "EMP-003");
// Array provisional: el modul 5 portara les colleccions
Prestec[] prestecs = {
new Prestec(javaEficac, marta, 100, 20),
new Prestec(patrons, diego, 100, 10),
new Prestec(refactor, nuria, 100, 95),
new Prestec(revista, marta, 100, 12),
new Prestec(dvd, diego, 100, 12)
};
// Estadistiques: un objecte en lloc de tres variables soltes
Prestec prestecRetardMesGran = null;
double recaptacio = 0.0;
System.out.println("Prestecs registrats:");
for (Prestec p : prestecs) {
System.out.println(" " + p);
recaptacio += p.calcularMulta();
if (prestecRetardMesGran == null
|| p.calcularDiesRetard() > prestecRetardMesGran.calcularDiesRetard()) {
prestecRetardMesGran = p;
}
}
System.out.printf("%nRecaptacio prevista: %.2f EUR%n", recaptacio);
System.out.println("Retard mes gran: "
+ (prestecRetardMesGran != null ? prestecRetardMesGran : "(cap)"));
// Devolucio completa del prestec mes greu
if (prestecRetardMesGran != null) {
prestecRetardMesGran.registrarDevolucio(
prestecRetardMesGran.getDiesTranscorreguts());
rebut.imprimirRebut(prestecRetardMesGran);
}
}
}Sortida:
=== BiblioTech 3.2 - Fi del modul 3 === Prestecs registrats: Prestec[PR-0001, material=Java Eficac, empleat=Marta Ruiz, transcorreguts=20, multa=1,25, LLEU] Prestec[PR-0002, material=Patrons de Disseny, empleat=Diego Alonso, transcorreguts=10, multa=0,00, SENSE RETARD] Prestec[PR-0003, material=Refactoritzacio, empleat=Nuria Vidal, transcorreguts=95, multa=20,00, GREU] Prestec[PR-0004, material=Java Magazine, empleat=Marta Ruiz, transcorreguts=12, multa=0,50, LLEU] Prestec[PR-0005, material=Curs de Spring, empleat=Diego Alonso, transcorreguts=12, multa=4,50, GREU] Recaptacio prevista: 26,25 EUR Retard mes gran: Prestec[PR-0003, material=Refactoritzacio, empleat=Nuria Vidal, transcorreguts=95, multa=20,00, GREU] ======================================== REBUT DE DEVOLUCIO - BIBLIOTECH Referencia: PR-0003 Material: Refactoritzacio Empleat: Nuria Vidal Retard: 80 dies Multa: 20,00 EUR Gravetat: GREU ========================================
Inventari del projecte després del mòdul 3
| Classe | Paquet | Responsabilitat |
|---|---|---|
Material |
domini |
Base dels suports: identificació, disponibilitat, regles de multa |
Llibre |
domini |
Suport amb 15 dies i 0,25 €/dia; afegeix autor i any |
Revista |
domini |
Suport amb 7 dies i 0,10 €/dia; afegeix número i periodicitat |
Dvd |
domini |
Suport amb 3 dies i 0,50 €/dia; afegeix durada |
Empleat |
domini |
Persona autoritzada; controla el seu límit de préstecs |
Prestec |
domini |
Relaciona material i empleat; calcula retard, multa i gravetat |
RebutConsola |
presentacio |
Genera els textos que veu l'usuari |
BiblioTechApp |
arrel | Arrencada i coordinació |
El que continua faltant, i ja saps on es resol:
| Mancança | Mòdul |
|---|---|
Material es pot instanciar; falta obligar a implementar getTipus() |
4 (classes abstractes) |
| No hi ha contractes de comportament reutilitzables entre classes sense parentiu | 4 (interfícies) |
| L'array de préstecs és rígid: no s'hi pot afegir ni buscar còmodament | 5 (col·leccions) |
| La validació avisa per consola en lloc de rebutjar de debò | 6 (excepcions) |
| En tancar el programa es perd tot | 7 (fitxers) |
Errors Habituals i Consells
- Sobreescriure
equalsi oblidarhashCode. L'error més car d'aquesta lliçó: no falla res fins que els objectes entren en unHashSeto unHashMap, i llavors falla en silenci. - Escriure
equals(LaMevaClasse)en lloc d'equals(Object). Sobrecàrrega en lloc de sobreescriptura.@Overrideho detecta a l'instant. - Fer servir camps diferents a
equalsihashCode. Trenca la clàusula 2 del contracte. Escriu-los sempre alhora i amb els mateixos camps. - Fer servir camps mutables a
equals/hashCode. Si el camp canvia mentre l'objecte és en una col·lecció, l'objecte es torna introbable dins d'ella. Fes servir campsfinal. - Comparar
doubleamb==dins d'equals.NaN != NaNtrenca la reflexivitat. Fes servirDouble.compare. - Formatar dades amb
toStringper processar-les. Si un altre codi analitza el teutoString, es converteix en una API que no podràs canviar. Per a això hi ha mètodes específics. toStringrecursiu entre objectes que es referencien.StackOverflowError. Imprimeix l'identificador de l'altre objecte, no l'objecte sencer.- Consell: deixa que l'IDE els generi. IntelliJ (
Alt+Insert→ equals() and hashCode()) i Eclipse (Alt+Shift+S) generen les tres implementacions amb el contracte correcte. Escriu-les a mà un cop per entendre-les, i després fes servir el generador. - Consell: si la classe és un contenidor de dades immutable, planteja't un
record(04-07): et dona els tres mètodes de franc i amb el contracte ben implementat. - Consell: prova el contracte. Quatre
asserto quatreprintlncomprovant reflexivitat, simetria, transitivitat inullcosten un minut i detecten el 90 % dels errors. Al mòdul 11 ho faràs amb JUnit.
Exercicis
Exercici 1: els tres mètodes a Material i les seves subclasses
Implementa toString, equals i hashCode a Material i decideix què han de fer les subclasses:
- A
Material, la igualtat es defineix perreferencia(l'ISBN d'un llibre, el codi d'un DVD...). - Fes servir
getClass()per garantir la simetria, i comprova amb sortida per consola que unLlibrei unDvdamb la mateixa referència no són iguals. - Sobreescriu
toStringaMaterialincloent-hi tipus, títol, referència i disponibilitat, i comprova-ho amb les quatre subclasses. - Raona per escrit si
Llibre,RevistaiDvdnecessiten sobreescriureequalsihashCode, o si en tenen prou d'heretar-los.
Exercici 2: demostrar la sobrecàrrega accidental
Escriu una classe de prova que demostri l'error de l'apartat 8 en la mateixa execució:
- Una classe
LlibreMalambpublic boolean equals(LlibreMal altre)(sense@Override). - Dos objectes amb el mateix ISBN.
- Compara'ls amb referències declarades com a
LlibreMali amb referències declarades com aObject, imprimint els dos resultats. - Afegeix
@Overridei copia el missatge exacte del compilador. - Corregeix la signatura i comprova que ara tots dos resultats coincideixen.
Exercici 3: verificador del contracte d'equals
Escriu un mètode static boolean verificarContracte(Object x, Object y, Object z) que comprovi les cinc propietats del contracte d'equals sobre tres objectes i imprimeixi un informe amb el resultat de cadascuna, més la coherència amb hashCode. Prova'l amb:
- Tres
Llibrecorrectes: dos amb el mateix ISBN i un de diferent. - Una classe
LlibreSenseHashCodeque sobreescriguiequalsperò nohashCode, per veure quina comprovació falla.
Solucions
Solució 1
package com.nexussoftware.bibliotech.domini;
import java.util.Objects;
public class Material {
// ... camps, constructor i metodes anteriors ...
/**
* Dos materials son iguals si son de la MATEIXA classe i comparteixen referencia.
* L'us de getClass() garanteix la simetria fins i tot amb subclasses.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Material altre = (Material) obj;
return referencia.equals(altre.referencia);
}
/** Mateix camp que equals: la referencia. */
@Override
public int hashCode() {
return Objects.hash(referencia);
}
@Override
public String toString() {
return String.format("%s[ref=%s, titol=%s, disponible=%b]",
getTipus(), referencia, titol, disponible);
}
}Comprovació de la simetria entre tipus diferents:
Llibre llibre = new Llibre("Java Eficac", "Joshua Bloch", "REF-001", 2018);
Dvd dvd = new Dvd("Curs de Spring", "REF-001", 240); // MATEIXA referencia
System.out.println("llibre.equals(dvd): " + llibre.equals(dvd)); // false
System.out.println("dvd.equals(llibre): " + dvd.equals(llibre)); // false (simetric)
System.out.println(llibre);
System.out.println(dvd);
Llibre altreExemplar = new Llibre("Java Eficac", "J. Bloch", "REF-001", 2017);
System.out.println("llibre.equals(altreExemplar): " + llibre.equals(altreExemplar)); // true
System.out.println("mateix hash: " + (llibre.hashCode() == altreExemplar.hashCode())); // trueSortida:
llibre.equals(dvd): false dvd.equals(llibre): false Llibre[ref=REF-001, titol=Java Eficac, disponible=true] DVD[ref=REF-001, titol=Curs de Spring, disponible=true] llibre.equals(altreExemplar): true mateix hash: true
Observa un detall elegant del toString de Material: fa servir getTipus(), que és polimòrfic, així que cada subclasse s'identifica sola sense necessitat de sobreescriure toString. És la lliçó 03-06 aplicada aquí.
4. Han de sobreescriure les subclasses equals i hashCode? No, i no ho haurien de fer. Tres raons:
- La referència ja identifica unívocament qualsevol material del catàleg: no hi ha dos llibres amb el mateix ISBN ni dos DVD amb el mateix codi. Afegir
autoroduracioMinutsa la comparació no distingiria res de nou. - Sobreescriure
equalsen una subclasse afegint-hi camps és precisament la font d'asimetries de l'apartat 12. Com queMaterial.equalsfa servirgetClass(), unLlibrei unDvdja no són mai iguals; el problema està resolt a la classe base. - Si necessitessin comparar més camps,
hashCodes'hauria d'actualitzar alhora a cada subclasse, multiplicant les oportunitats d'error.
Regla que se'n dedueix: defineix la igualtat una sola vegada, al nivell més alt de la jerarquia on tingui sentit, i fes servir un camp identificador estable en lloc de la suma de tots els camps.
Solució 2
package com.nexussoftware.bibliotech;
public class DemoEqualsMal {
static class LlibreMal {
final String isbn;
LlibreMal(String isbn) { this.isbn = isbn; }
// SOBRECARREGA, no sobreescriptura: la signatura rep LlibreMal, no Object
public boolean equals(LlibreMal altre) {
System.out.println(" [s'executa EL MEU equals(LlibreMal)]");
return isbn.equals(altre.isbn);
}
}
static class LlibreBe {
final String isbn;
LlibreBe(String isbn) { this.isbn = isbn; }
@Override
public boolean equals(Object obj) {
System.out.println(" [s'executa EL MEU equals(Object)]");
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
return isbn.equals(((LlibreBe) obj).isbn);
}
@Override
public int hashCode() { return isbn.hashCode(); }
}
public static void main(String[] args) {
System.out.println("--- Versio INCORRECTA ---");
LlibreMal a = new LlibreMal("978-0000000001");
LlibreMal b = new LlibreMal("978-0000000001");
System.out.println(" Amb tipus declarat LlibreMal:");
System.out.println(" a.equals(b) = " + a.equals(b));
Object oa = a;
Object ob = b;
System.out.println(" Amb tipus declarat Object:");
System.out.println(" oa.equals(ob) = " + oa.equals(ob));
System.out.println("\n--- Versio CORRECTA ---");
LlibreBe c = new LlibreBe("978-0000000001");
LlibreBe d = new LlibreBe("978-0000000001");
System.out.println(" Amb tipus declarat LlibreBe:");
System.out.println(" c.equals(d) = " + c.equals(d));
Object oc = c;
Object od = d;
System.out.println(" Amb tipus declarat Object:");
System.out.println(" oc.equals(od) = " + oc.equals(od));
}
}Sortida:
--- Versio INCORRECTA ---
Amb tipus declarat LlibreMal:
[s'executa EL MEU equals(LlibreMal)]
a.equals(b) = true
Amb tipus declarat Object:
oa.equals(ob) = false
--- Versio CORRECTA ---
Amb tipus declarat LlibreBe:
[s'executa EL MEU equals(Object)]
c.equals(d) = true
Amb tipus declarat Object:
[s'executa EL MEU equals(Object)]
oc.equals(od) = trueLes traces ho deixen claríssim: a la versió incorrecta, la crida a través d'Object ni tan sols entra al teu mètode —no apareix el missatge—, perquè el compilador ha resolt la crida cap a Object.equals. A la correcta, el teu mètode s'executa en tots dos casos.
4. Missatge del compilador en afegir @Override:
Cinc paraules que t'haurien estalviat l'error sencer. En un HashSet (mòdul 5), la versió incorrecta produiria duplicats sense cap senyal d'error.
Solució 3
package com.nexussoftware.bibliotech;
/** Verificador didactic del contracte d'equals i la seva coherencia amb hashCode. */
public class VerificadorContracte {
public static boolean verificarContracte(Object x, Object y, Object z) {
System.out.println("Verificant el contracte amb:");
System.out.println(" x = " + x);
System.out.println(" y = " + y);
System.out.println(" z = " + z);
boolean totCorrecte = true;
// 1. Reflexiva
boolean reflexiva = x.equals(x) && y.equals(y) && z.equals(z);
informar("Reflexiva (x.equals(x))", reflexiva);
totCorrecte &= reflexiva;
// 2. Simetrica
boolean simetrica = (x.equals(y) == y.equals(x))
&& (y.equals(z) == z.equals(y))
&& (x.equals(z) == z.equals(x));
informar("Simetrica (x=y implica y=x)", simetrica);
totCorrecte &= simetrica;
// 3. Transitiva: nomes es comprova si l'antecedent es compleix
boolean transitiva = true;
if (x.equals(y) && y.equals(z)) {
transitiva = x.equals(z);
}
informar("Transitiva (x=y i y=z implica x=z)", transitiva);
totCorrecte &= transitiva;
// 4. Consistent: mateixes crides, mateixos resultats
boolean consistent = (x.equals(y) == x.equals(y)) && (x.equals(y) == x.equals(y));
informar("Consistent (crides repetides)", consistent);
totCorrecte &= consistent;
// 5. Davant de null
boolean davantDeNull = !x.equals(null) && !y.equals(null) && !z.equals(null);
informar("Null (x.equals(null) es false)", davantDeNull);
totCorrecte &= davantDeNull;
// 6. Coherencia amb hashCode (clausula 2 del contracte de hashCode)
boolean coherentHash = true;
if (x.equals(y) && x.hashCode() != y.hashCode()) coherentHash = false;
if (y.equals(z) && y.hashCode() != z.hashCode()) coherentHash = false;
if (x.equals(z) && x.hashCode() != z.hashCode()) coherentHash = false;
informar("hashCode (iguals -> mateix hash)", coherentHash);
totCorrecte &= coherentHash;
System.out.println(totCorrecte ? "RESULTAT: contracte COMPLERT\n"
: "RESULTAT: contracte TRENCAT\n");
return totCorrecte;
}
private static void informar(String propietat, boolean ok) {
System.out.printf(" %-38s %s%n", propietat, ok ? "OK" : "FALLA");
}
/** Classe defectuosa a proposit: equals sense hashCode. */
static class LlibreSenseHashCode {
final String isbn;
LlibreSenseHashCode(String isbn) { this.isbn = isbn; }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
return isbn.equals(((LlibreSenseHashCode) obj).isbn);
}
@Override
public String toString() { return "LlibreSenseHashCode[" + isbn + "]"; }
// Falta hashCode() a proposit
}
public static void main(String[] args) {
System.out.println("=== CAS 1: Llibre correcte ===");
Llibre x = new Llibre("Java Eficac", "Joshua Bloch", "978-0000000001", 2018);
Llibre y = new Llibre("Java Eficac", "J. Bloch", "978-0000000001", 2017);
Llibre z = new Llibre("Refactoritzacio", "Martin Fowler", "978-0000000003", 2018);
verificarContracte(x, y, z);
System.out.println("=== CAS 2: equals sense hashCode ===");
LlibreSenseHashCode p = new LlibreSenseHashCode("978-0000000001");
LlibreSenseHashCode q = new LlibreSenseHashCode("978-0000000001");
LlibreSenseHashCode r = new LlibreSenseHashCode("978-0000000003");
verificarContracte(p, q, r);
}
}Sortida:
=== CAS 1: Llibre correcte === Verificant el contracte amb: x = Llibre[isbn=978-0000000001, titol=Java Eficac, autor=Joshua Bloch, any=2018, disponible=true] y = Llibre[isbn=978-0000000001, titol=Java Eficac, autor=J. Bloch, any=2017, disponible=true] z = Llibre[isbn=978-0000000003, titol=Refactoritzacio, autor=Martin Fowler, any=2018, disponible=true] Reflexiva (x.equals(x)) OK Simetrica (x=y implica y=x) OK Transitiva (x=y i y=z implica x=z) OK Consistent (crides repetides) OK Null (x.equals(null) es false) OK hashCode (iguals -> mateix hash) OK RESULTAT: contracte COMPLERT === CAS 2: equals sense hashCode === Verificant el contracte amb: x = LlibreSenseHashCode[978-0000000001] y = LlibreSenseHashCode[978-0000000001] z = LlibreSenseHashCode[978-0000000003] Reflexiva (x.equals(x)) OK Simetrica (x=y implica y=x) OK Transitiva (x=y i y=z implica x=z) OK Consistent (crides repetides) OK Null (x.equals(null) es false) OK hashCode (iguals -> mateix hash) FALLA RESULTAT: contracte TRENCAT
El revelador del cas 2: l'equals és impecable —compleix les cinc propietats— i tot i així la classe està trencada. Aquest és exactament el motiu que la fallada sigui tan difícil de trobar en un projecte real: totes les proves d'igualtat passen, i el sistema falla molt més tard, dins d'un HashMap, sense llançar cap excepció.
Dos apunts tècnics sobre el verificador. Primer, la comprovació de transitivitat només té sentit quan l'antecedent es compleix; si x i y no són iguals, la implicació és certa per vacuïtat i no hi ha res a verificar. Segon, aquest verificador és un avançament del que faràs al mòdul 11 amb JUnit, on aquestes comprovacions s'escriuen com a proves automàtiques que s'executen a cada compilació en lloc d'imprimir per consola.
Conclusió
Has tancat el cercle. Saps que Object és l'arrel de tota la jerarquia i en coneixes els mètodes, inclosos els que no has de tocar (getClass), els desaconsellats (clone), els obsolets (finalize) i els que esperen al mòdul 8 (wait/notify). Entens què és el críptic Llibre@1b6d3586 —el nom de la classe i el hashCode en hexadecimal, no una adreça de memòria— i saps substituir-lo per un toString breu, informatiu i sense efectes secundaris que transforma la depuració. Distingeixes la igualtat de referència de la igualtat lògica, i saps que decidir quina s'aplica és una decisió del domini: a BiblioTech, dos llibres són el mateix llibre si comparteixen ISBN. Domines el contracte complet d'equals —reflexiu, simètric, transitiu, consistent i fals davant de null— i la seva implementació canònica en cinc passos, amb la variant moderna de l'instanceof amb patró. Has vist en execució l'error clàssic d'escriure equals(Llibre) en lloc d'equals(Object), i per què @Override és la diferència entre un segon i una tarda perduda. Coneixes el contracte de hashCode, la regla inquebrantable de sobreescriure'l sempre al costat d'equals i amb els mateixos camps, Objects.hash per escriure'l en una línia, i exactament què es trenca si l'oblides: un HashSet amb duplicats i un equals a qui ningú no arriba a cridar. I afegeixes al teu vocabulari Objects.equals i Objects.requireNonNull, la primera forma correcta de rebutjar un argument invàlid que veus al curs.
Amb això tanques el mòdul 3, i BiblioTech és un altre programa. On hi havia vint variables soltes en un main de dues-centes línies, ara hi ha vuit classes repartides en tres paquets: una jerarquia Material amb tres suports que apliquen les seves pròpies regles per polimorfisme, un Empleat que fa complir el seu límit, un Prestec que calcula el seu venciment en néixer i registra devolucions completes en una sola crida, una capa de presentació separada del domini, i objectes que s'imprimeixen i es comparen com cal. Els camps són privats, els invariants estan garantits, l'API pública està podada i afegir un suport nou costa una classe i zero modificacions. I aquelles tres variables incòmodes —retardMesGran, empleatRetardMesGran i llibreRetardMesGran— són avui un únic objecte Prestec coherent, tal com es va prometre en acabar el mòdul 2.
Al mòdul 4, POO Avançada, faràs el salt dels mecanismes bàsics als que fan servir de debò les biblioteques professionals de Java. Convertiràs Material en una classe abstracta que no es pugui instanciar i que obligui les seves subclasses a implementar el que els toca; descobriràs les interfícies, amb què una classe es pot comprometre a diversos contractes alhora sense herència múltiple; coneixeràs les classes internes i anònimes, les expressions lambda i les referències a mètodes que canvien per complet la manera d'escriure Java modern; i acabaràs amb enumeracions i records, que substituiran aquelles cadenes "LLEU" i "GREU" per tipus segurs i generaran sols els tres mètodes que acabes d'escriure a mà. BiblioTech està a punt per créixer.
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
