11  Utilització d’objectes

Objectius

  • Explicar què és un objecte i quines característiques té (estat, comportament i identitat).
  • Distingir entre classe i objecte.
  • Instanciar objectes amb l’operador new i guardar-los en variables (referències).
  • Cridar mètodes sobre un objecte i passar-los paràmetres.
  • Accedir a les propietats d’un objecte.
  • Distingir els membres d’instància dels membres estàtics (static).
  • Entendre el paper dels constructors en la creació d’objectes.
  • Comprendre com Java allibera la memòria amb el recol·lector de brossa (garbage collector).

Fins ara has escrit programes com una recepta: una llista d’instruccions que manipulen variables soltes (int, double, String, arrays…). Aquesta manera de treballar es diu programació estructurada.

A partir d’ara faràs un salt de mentalitat. Enlloc de pensar en dades i passos separats, pensaràs en objectes: peces que agrupen dins seu unes dades i les operacions que hi actuen. Aquest capítol és la porta d’entrada a la programació orientada a objectes (POO).

11.1 Què és un objecte?

Un objecte és una entitat del programa que representa “una cosa” del món (o del problema) i que conté:

  • Estat: les dades que descriuen aquesta cosa en un moment donat. També se’n diu atributs o propietats.
  • Comportament: les accions que aquesta cosa pot fer o que li podem demanar. Es materialitza en mètodes.
  • Identitat: cada objecte és únic i es distingeix de qualsevol altre, encara que tingui el mateix estat.
ConsellAnalogia: un cotxe concret

Pensa en el teu cotxe. El seu estat és el color, la matrícula, els litres de combustible que li queden i la velocitat actual. El seu comportament és accelerar, frenar o tocar el clàxon. La seva identitat és que és aquest cotxe i no un altre: encara que el veí en tingui un d’idèntic (mateixa marca, mateix color), són dos cotxes diferents.

Classe i objecte no són el mateix

Molt sovint es confonen. La diferència és fonamental:

  • Una classe és el motlle o el plànol: descriu quines dades i quins comportaments tindran els objectes d’aquell tipus. La classe no ocupa “coses reals”, és una definició.
  • Un objecte és un exemplar concret creat a partir d’aquell motlle. També se’n diu instància.

%%{init: {"theme": "neutral", "themeVariables": {"noteBkgColor": "#eef7fd", "noteBorderColor": "#31BAE9", "noteTextColor": "#495057"}}}%%
classDiagram
    class Llibre {
        +String titol
        +String autor
        +boolean prestat
        +prestar()
        +retornar()
    }
    note "La CLASSE és el plànol.<br />Cada objecte és una còpia concreta feta amb aquest plànol."

D’una sola classe Llibre en pots crear molts objectes: El Quixot, Mecanoscrit del segon origen, Solenoide… Tots comparteixen la mateixa estructura (tenen títol, autor i estat de préstec), però cadascun té els seus propis valors.

Aspecte Classe Objecte
Què és Definició / motlle Exemplar concret (instància)
Quantitat N’hi ha una En pots crear molts
Existeix quan S’escriu el codi S’executa el programa (new)
Ocupa memòria No (és una plantilla) Sí (cada objecte té el seu espai)
Exemple Llibre El Quixot, Solenoide
NotaJa has fet servir objectes sense saber-ho

Quan escrivies Scanner teclat = new Scanner(System.in); (Capítol 4) ja creaves un objecte de la classe Scanner! I String, que has fet servir contínuament, també és una classe amb els seus mètodes (length(), toUpperCase()…). La POO ja hi era; ara la faràs explícita.

Mini-repte. Descriu amb paraules una classe Soci d’una biblioteca: quin estat (atributs) creus que hauria de tenir i quins tres comportaments (mètodes) li demanaries.

11.2 Crear objectes: l’operador new

Per crear un objecte s’usa la paraula clau new seguida del nom de la classe i uns parèntesis. Aquest procés es diu instanciació.

// new demana memòria per a un objecte nou i el retorna
Scanner teclat = new Scanner(System.in);
Random daus = new Random();

Analitzem-ho pas a pas amb un exemple d’una classe imaginària Comptador:

Comptador c = new Comptador();

Aquí passen tres coses alhora:

  1. new Comptador() demana a la JVM un tros de memòria per a un objecte Comptador nou i l’inicialitza (crida el constructor, ho veurem més avall).
  2. new retorna una referència a aquest objecte: una mena d’adreça que ens diu on és.
  3. La referència es guarda a la variable c, de tipus Comptador.

11.2.1 Variables de referència: l’objecte no viu dins la variable

Aquest és un dels punts que més costa al principi. Amb els tipus primitius (int, double…), la variable conté el valor. Amb els objectes, la variable no conté l’objecte: conté una referència cap a ell. L’objecte viu en un altre lloc de la memòria.

ConsellAnalogia: el comandament i el televisor

La variable és com el comandament a distància; l’objecte és el televisor. El comandament no és el televisor: només hi apunta. Pots tenir dos comandaments que controlen el mateix televisor, i pots tenir un comandament que encara no apunta a cap televisor.

Llibre a = new Llibre("El Quixot", "Cervantes");
Llibre b = a;   // b NO és un llibre nou: apunta AL MATEIX objecte que a

Aquí només hi ha un objecte Llibre a la memòria, i dues variables (a i b) que hi apunten. Si modifiques l’objecte a través de b, el veuràs canviat també a través de a, perquè és el mateix objecte.

flowchart LR
    a["a"] --> obj["Objecte Llibre<br /> titol = El Quixot"]
    b["b"] --> obj

11.2.2 null: una referència que no apunta enlloc

Una variable de referència pot no apuntar a cap objecte. Aquest valor especial es diu null.

Llibre preferit = null;   // Encara no tinc cap llibre preferit
AlertaError típic: NullPointerException

Si intentes fer servir un objecte a través d’una referència que val null, el programa peta amb una NullPointerException (sovint abreujada NPE). És l’error més habitual en Java.

Llibre preferit = null;
System.out.println(preferit.getTitol());  // ERROR en execució: NullPointerException

No hi ha cap objecte on preguntar el títol! Abans d’usar una referència, assegura’t que apunta a un objecte de veritat (que no és null).

Mini-repte. Escriu dues línies que creïn dos objectes Random diferents. Després escriu-ne una tercera que faci que una nova variable apunti al primer d’ells (sense crear-ne cap més).

11.3 Cridar mètodes i passar paràmetres

Un objecte no és una caixa de dades morta: li podem demanar coses. Es fa amb l’operador punt (.): s’escriu la referència, un punt, el nom del mètode i, entre parèntesis, els arguments (els valors que li passem).

referencia.nomDelMetode(arguments)

Vegem-ho amb la classe String, que ja coneixes:

String nom = "biblioteca";
int longitud = nom.length();          // mètode sense paràmetres → 10
String majuscules = nom.toUpperCase(); // → "BIBLIOTECA"
boolean comenca = nom.startsWith("bib"); // mètode amb un paràmetre → true

11.3.1 Paràmetres i valor de retorn

  • Els paràmetres són les dades d’entrada que el mètode necessita per fer la seva feina. Van entre parèntesis. Un mètode pot no tenir-ne cap, o tenir-ne diversos separats per comes.
  • El valor de retorn és el resultat que el mètode et torna. El pots guardar en una variable o usar directament. Si un mètode no retorna res, el seu tipus de retorn és void.
Random daus = new Random();
int tirada = daus.nextInt(6) + 1;   // paràmetre 6, retorna un int entre 1 i 6
System.out.println("Ha sortit un " + tirada);
ConsellAnalogia: una màquina de cafè

Cridar un mètode és com prémer un botó de la màquina de cafè. Els paràmetres són les opcions que tries (quantitat de sucre, mida del got). El valor de retorn és el cafè que en surt. Alguns botons no et tornen res visible (per exemple, “netejar”): serien mètodes void.

11.3.2 Encadenar crides

Com que molts mètodes retornen un objecte, pots encadenar crides una darrere l’altra:

String resultat = "  Hola Món  ".trim().toLowerCase();  // → "hola món"

trim() retorna un String sense espais, i sobre aquest resultat cridem toLowerCase().

AlertaError típic: oblidar els parèntesis

Un mètode SEMPRE porta parèntesis, encara que no tingui paràmetres. nom.length (sense parèntesis) no crida el mètode; provoca un error de compilació. Ha de ser nom.length().

Mini-repte. Donada String frase = "Programació Orientada a Objectes";, escriu una expressió que retorni quants caràcters té la frase en majúscules.

11.4 Accedir a les propietats

A més de cridar mètodes, de vegades es pot llegir o modificar directament l’estat d’un objecte a través dels seus atributs, també amb l’operador punt:

Punt p = new Punt();
p.x = 3;        // escriure una propietat
p.y = 5;
System.out.println(p.x + ", " + p.y);  // llegir una propietat → 3, 5
NotaCompte: l’accés directe no sempre és bona idea

Poder tocar directament p.x és còmode, però trencar la protecció de les dades d’un objecte no és una bona pràctica. Al Capítol 12 veuràs l’encapsulació: normalment els atributs es fan privats i s’hi accedeix amb mètodes controlats (getters i setters). De moment, quede’t amb la idea que un objecte té estat i que s’hi pot arribar.

11.5 Membres d’instància i membres estàtics

Fins ara tot el que hem cridat pertanyia a un objecte concret: teclat.nextLine() actua sobre aquell Scanner. Es diuen membres d’instància perquè necessiten una instància (un objecte) per funcionar.

Però hi ha membres que pertanyen a la classe sencera, no a cap objecte en particular. Es marquen amb la paraula clau static i es criden usant el nom de la classe en comptes d’una referència.

// Mètode estàtic: es crida amb el nom de la classe
double arrel = Math.sqrt(144);      // → 12.0
int gran = Math.max(7, 3);          // → 7

// Constant estàtica
double pi = Math.PI;                // → 3.141592...

No has creat mai cap objecte Math amb new Math(), i no cal: sqrt, max o PI són estàtics, viuen a la classe.

ConsellAnalogia: el gerent i els treballadors

Imagina una botiga. Cada treballador (objecte) atén els seus propis clients: és un membre d’instància. Però hi ha informació de tota la botiga —l’horari, el nom de l’empresa, el total de vendes del dia— que no pertany a cap treballador concret, sinó a la botiga sencera. Això seria un membre static.

Membre d’instància Membre estàtic (static)
Pertany a Cada objecte La classe sencera
Com es crida objecte.membre Classe.membre
Necessita new? No
Còpies en memòria Una per objecte Una de sola, compartida
Exemple teclat.nextLine() Math.sqrt(9)
AlertaError típic: cridar un mètode d’instància com si fos estàtic
int n = Integer.parseInt("42");     // BÉ: parseInt és static
String s = String.valueOf(42);      // BÉ: valueOf és static

Scanner.nextLine();                 // ERROR: nextLine NO és static;
                                    // necessita un objecte Scanner concret

Si l’IDE et diu “non-static method cannot be referenced from a static context”, és que estàs cridant un mètode d’instància sense un objecte al davant.

Mini-repte. La classe Integer té el mètode estàtic Integer.parseInt(String). Escriu una línia que converteixi el text "128" en un int i el guardi en una variable.

11.6 Els constructors

Quan escrius new Scanner(System.in), els parèntesis del final no són decoratius: estàs cridant un constructor. Un constructor és un bloc de codi especial que s’executa just en crear l’objecte i que serveix per inicialitzar-lo (donar valors inicials al seu estat, preparar recursos, etc.).

// El "System.in" és un argument que rep el constructor de Scanner
Scanner teclat = new Scanner(System.in);

// Molts constructors admeten diferents arguments (o cap)
Random r1 = new Random();       // Constructor sense arguments
Random r2 = new Random(42);     // Constructor amb una llavor

Característiques clau d’un constructor:

  • Es diu exactament igual que la classe.
  • No té tipus de retorn (ni tan sols void).
  • Es crida amb new i només aquell moment.

Aprendràs a escriure els teus propis constructors al Capítol 12. De moment, l’important és que entenguis que els arguments que poses dins el new(...) van a parar al constructor per deixar l’objecte a punt.

ConsellAnalogia: donar d’alta un soci

Crear un objecte Soci és com fer la fitxa d’alta a la biblioteca. El constructor és el formulari inicial: en el moment de crear el soci li poses el nom i el DNI. Sense aquest pas, tindries una fitxa buida i inservible.

Mini-repte. Busca a la documentació (o a l’IDE) quants constructors diferents té la classe StringBuilder. Escriu una línia que en creï un a partir del text inicial "Hola".

11.7 Destrucció d’objectes i recol·lector de brossa

En alguns llenguatges (com C o C++), quan un objecte ja no es necessita, el programador ha d’alliberar-ne la memòria manualment. Si se n’oblida, la memòria es va omplint (una fuita de memòria).

En Java això és automàtic. La JVM té el recol·lector de brossa (garbage collector, GC): un procés que, de tant en tant, busca objectes que ja no són accessibles (cap variable hi apunta) i n’allibera la memòria sense que tu hi facis res.

Llibre ll = new Llibre("Efímer", "Anònim");
ll = null;   // Ara ningú apunta a l'objecte anterior:
             // El GC el podrà eliminar quan vulgui
ConsellAnalogia: la recollida de residus

Tu no has de portar les escombraries a l’abocador municipal: deixes la bossa i el servei de neteja passa quan toca. El GC és aquest servei: tu deixes d’usar objectes i ell els recull quan li va bé. No controles el moment exacte en què passa.

NotaI finalize()? Millor oblidar-se’n

Antigament hi havia un mètode finalize() per executar codi just abans que el GC eliminés un objecte. Està desaconsellat i obsolet: el seu moment d’execució és imprevisible. Per alliberar recursos (fitxers, connexions) s’usen altres mecanismes, com try-with-resources (Capítol 8).

AlertaError típic: pensar que null “esborra” l’objecte a l’instant

Posar una referència a null no destrueix l’objecte immediatament ni allibera memòria a l’acte. Només trenca l’enllaç. L’objecte desapareixerà quan el GC decideixi passar, cosa que no controles.

Mini-repte. Explica amb les teves paraules per què aquest fragment deixa un objecte “orfe” (sense cap referència) i què li passarà:

Random r = new Random();
r = new Random();

11.8 Resum

  • Un objecte agrupa estat (atributs), comportament (mètodes) i té identitat pròpia.
  • Una classe és el motlle; un objecte és una instància concreta feta amb aquest motlle. De cada classe se’n poden crear molts objectes.
  • Els objectes es creen amb new, que crida el constructor i retorna una referència.
  • Les variables d’objecte no contenen l’objecte, sinó una referència cap a ell. Dues variables poden apuntar al mateix objecte. Una referència pot valer null.
  • Els mètodes es criden amb l’operador punt (.), amb els paràmetres entre parèntesis, i poden retornar un valor.
  • Els membres estàtics (static) pertanyen a la classe (Math.sqrt), no a cap objecte; els membres d’instància necessiten un objecte concret.
  • El recol·lector de brossa allibera automàticament la memòria dels objectes que ja no són accessibles.

11.9 Per practicar

  1. Objectes quotidians. Tria tres coses del teu voltant (una motxilla, un mòbil, un rellotge). Per a cadascuna, escriu en paper 3 atributs (estat) i 3 mètodes (comportament).

  2. Traça de referències. Sense executar-ho, dibuixa què apunta a què després d’aquest codi i digues què imprimeix (assumeix que Punt té atributs públics x i y):

Punt p = new Punt();
p.x = 1;
Punt q = p;
q.x = 9;
System.out.println(p.x);
  1. Estàtic o d’instància? Classifica: Math.random(), teclat.nextInt(), Integer.parseInt("5"), "hola".length(), System.out.println("hi").

  2. Caça la NPE. El codi següent llança NullPointerException. Explica per què i corregeix-lo:

String s;
System.out.println(s.length());
  1. Experiment amb Random. Crea dos objectes Random amb la mateixa llavor (new Random(1)) i imprimeix el primer nextInt(100) de cadascun. Què observes? Per què creus que passa?