Omet navegació

3.- Seriació d'objectes

 

Seriació d'objectes

La tècnica de la seriació és segurament la més senzilla de totes, però també a la vegada la més problemàtica. Java disposa d’un sistema genèric de seriació de qualsevol objecte, un sistema recursiu que es repeteix per cada objecte contingut a la instància que s’està seriant. Aquest procés para en arribar als tipus primitius, els quals es guarden com una sèrie de bytes. A banda dels tipus primitius, Java serialitza també molta informació addicional o metadades específiques de cada classe (el nom de les classe, els noms dels atributs i molta més informació addicional). Gràcies a les metadades es fa possible automatitzar la seriació de forma genèrica amb garanties de recuperar un objecte tal com es va guardar.

Lamentablement, aquest és un procediment específic de Java. És a dir, no és possible recuperar els objectes seriats des de Java utilitzant un altre llenguatge. D’altra banda, el fet de guardar metadades pot arribar a comportar també problemes, encara que utilitzem sempre el llenguatge Java. La modificació d’una classe pot fer variar les seues metadades. Aquestes variacions poden donar problemes de recuperació d’instàncies que hagen estat guardades amb algunes versions anteriors a la modificació, impedint que l’objecte puga ser recuperat.

Aquestes consideracions desestimen aquesta tècnica per guardar objectes de forma més o menys permanent. En canvi, la seua senzillesa la fa una perfecta candidata per a l’emmagatzematge temporal, per exemple dins de la mateixa sessió.

Per a que un objecte puga ser seriat cal que la seua classe i tot el seu contingut implementen la interfície Serializable. Es tracta d’una interfície sense mètodes, perquè l’únic objectiu de la interfície és actuar de marcador per indicar a la màquina virtual quines classes es poden seriar i quines no.

Totes les classes equivalents als tipus bàsics ja implementen Serializable. També implementen aquesta interfície la classe String i tots els contenidors i els objectes Array. La seriació de col·leccions depèn en últim terme dels elements continguts. Si aquestos són seriables, la col·lecció també ho serà.

En cas que la classe de l’objecte que s’intente seriar, o les d’algun dels objectes que continga, no implementaren la interfície Serializable, es llançaria una excepció de tipus NotSerializableException, impedint l’emmagatzematge.

Els Streams ObjectInputStream i ObjectOutputStream són decoradors que afegeixen a qualsevol altre Stream la capacitat de seriar qualsevol objecte Serializable. El stream d'eixida disposarà del mètode writeObject. i el stream d’entrada, el mètode de lectura readObject.

El mètode readObject només permet recuperar instàncies que siguen de la mateixa classe que la que es va guardar. En cas contrari, es llançaria una excepció de tipus ClassCastExeception. A més, cal que l’aplicació dispose del codi compilat de la classe; si no fóra així, l’excepció llançada seria ClassNotFoundException.

 

Exemple

Ens recolzarem en un exemple basat en els anteriors, en els empleats. Ara anem a suposar que els empleats són objectes, i intentarem guardar aquestos objectes en un fitxer amb una seriació.

El primer pas serà construir la classe Empleat, que contindrà la mateixa informació que en els altres apartats: número d'empleat, nom, departament, edat i sou. A banda de les propietats per a cadascuna de les dades anteriors (que seran del mateix tipus que en les altres ocasions), també farem el constructor que inicialitza l'objecte, així com els mètodes get per a cadascuna de les propietat. No ens caldran els mètodes set, ja que en els exemples sempre utilitzarem el constructor. Observeu com la classe ha de ser serialitzable.

import java.io.Serializable;

public class Empleat implements Serializable {
private int num=0;
private String nom=null;
private int departament=0;
private int edat=0;
private double sou=0.0;

public Empleat(){
}

public Empleat(int num, String nom, int dep, int edat, double sou){
this.num=num;
this.nom=nom;
this.departament=dep;
this.edat=edat;
this.sou=sou;
}

public int getNum(){
return this.num;
}

public String getNom(){
return this.nom;
}

public int getDepartament(){
return this.departament;
}

public int getEdat(){
return this.edat;
}

public double getSou(){
return this.sou;
}
}

 

Anem a intentar construir el fitxer. El flux de dades serà un ObjectOutputStream per a poder escriure (writeObject). I observeu com s'ha de recolzar en un OutputStream, que en aquest cas serà d'un fitxer, és a dir un FileOutputStream. A cada iteració del bucle senzillament construirem un objecte de la classe Empleat i l'escriurem al fitxer.

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;


public class CrearEmpleatsObjecte {

public static void main(String[] args) throws IOException {
ObjectOutputStream f = new ObjectOutputStream(new FileOutputStream("Empleats.obj"));

String[] noms = {"Andreu","Bernat","Clàudia","Damià"};
int[] departaments = {10,20,10,10};
int[] edats = {32,28,26,40};
double[] sous = {1000.0,1200.0,1100.0,1500.0};
Empleat e;

for (int i=0;i<4;i++){
e = new Empleat(i+1,noms[i],departaments[i],edats[i],sous[i]);
f.writeObject(e);
}

f.close();
}
}

Nota

El fitxer creat, Empleats.obj, evidentment no és de text. Tanmateix si l'obrim amb un editor de text podrem veure alguna cosa.

  • La primera qüestió és que es guarda el nom de la classe amb el nom del paquet davant. Exemples.Empleat és realment el nom de la classe creada.
  • Es guarden també els noms dels camps. Tot això són les metadades que havíem comentat, i que permeten la recuperació posterior dels objectes guardats
  • I després ja podem veure la informació guardada, on identifiquem els noms dels empleats

Per a llegir el fitxer creat, Empleats.obj, utilitzarem el ObjectInputStream per a poder fer readObject. S'ha de basar en un InputStream, que en aquest cas serà un FileInputStream. El tractament de final de fitxer el farem capturant l'excepció (l'error) d'haver arribat al final i intentat llegir encara: EOFException. La raó és que readObject no torna null, a no ser que s'haja introduït aquest valor. Per tant muntem un bucle infinit, però capturant amb try ... catch l'error, que és quan tancarem el Stream.

import java.io.EOFException;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ConsultarEmpleatsObjecte {

public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream f = new ObjectInputStream(new FileInputStream("Empleats.obj"));

Empleat e;
try {
while (true) {
e = (Empleat) f.readObject();
System.out.println("Número: " + e.getNum());
System.out.println("Nom: " + e.getNom());
System.out.println("Depart: " + e.getDepartament());
System.out.println("Edat: " + e.getEdat());
System.out.println("Sou: " + e.getSou());
System.out.println();
}
} catch (EOFException eof) {
f.close();
}
}
}