Serialização
Por serialização entendemos o processo de converter todo o estado de objecto num vector de bytes de modo a que seja possível, mais tarde, pegar nesse vector e reconstruir o estado do nosso objecto. Este vector de bytes pode ser guardado num ficheiro, em memória, ou enviado para outra máquina através de rede1.
A serialização pode ser usada para diversas situações, embora não seja um mecanismo de substituição para escrita normal em ficheiros, nem pretenda substituir qualquer mecanismo de persistência de dados, no entanto é um mecanismo simples e versátil. Na plataforma Java, o mecanismo de serialização pode ser controlado por cada classe, podendo a classe usar o comportamento por omissão, usar a persistência automática, persistência ajustável ou persistência manual.
No comportamento por omissão, a classes definidas por nós não são serializáveis e o sistema de persistência não irá gravar as classes.
Usando a persistência automática, a classe é obrigada a implementar a interface Serializable mas não precisa implementar qualquer outro método. Neste caso, o mecanismo de persistência irá gravar a nossa classe, todos os atributos primitivos (int, float, double, etc) e todos os objectos que também implementem a interface Serializable. Caso algum dos objectos que é atributo da nossa classe e que não implemente a interface Serializable, o mecanismo de persistência falha a gravação dos dados. Este processo exclui da persistência todos os atributos que sejam marcados como static e todos os que forem marcados como transient.
public class Empregado implements Serializable {
private String nome;
private int numero;
private Date data;
public Empregado(String n, int num) {
System.out.println("Construtor c/ argumentos");
nome = n;
numero = num;
data = new Date();
}
public String toString() {
return "empregado [ " + nome + ", " + numero + ", " + data + "]";
}
//Método para testar a persistência automática.
//Apenas escreve o objecto e lê imediatamente a seguir.
public void experimentar() {
try {
ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("empregados.dat")));
System.out.println("Escreveu: " + this);
out.writeObject(this);
out.close();
ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream("empregados.dat")));
Empregado lido = (Empregado)in.readObject();
System.out.println("Leu: " + lido);
in.close();
} catch (Exception e) {
System.err.println(e);
}
}
}
Com persistência ajustável, a nossa classe tem de implementar a interface Serializable e definir os métodos readObject e writeObject. Nestes métodos podemos controlar o processo de gravação dos dados, e a ordem pela qual gravarmos os nossos atributos, no método writeObject, tem de ser a ordem pela qual lemos os atributos quando deserializamos a classe, no método readObject.
public class Empregado implements Serializable {
private String nome;
private int numero;
private Date data;
public Empregado(String n, int num) {
System.out.println("Construtor c/ argumentos");
nome = n;
numero = num;
data = new Date();
}
public String toString() {
return "empregado [ " + nome + ", " + numero + ", " + data + "]";
}
/**
* Nos dois métodos seguintes optámos por implementar tudo manualmente,
* processo que podemos ver descrito mais abaixo, mas podíamos ter usado
* um misto de persistência manual com automática. Através do método
* defaultReadObject() temos acesso ao processo de persistência automático.
*/
//Ler pela ordem de escrita.
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
nome = (String) in.readObject();
data = (Date) in.readObject();
numero = in.readInt();
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(nome);
out.writeObject(data)
out.writeInt(numero);
}
}
Finalmente, a persistência manual, é conseguida implementando a interface Externalizable, os métodos readExternal(ObjectInput in) e writeExternal(ObjectInput out) e tendo um construtor sem argumentos na classe. Esta imposição é específica deste tipo de persistência e não se verifica nos casos anteriores.
Este sistema, como é manual, obriga a que o objecto seja lido exactamente pela ordem em que foi escrito e que todos os atributos sejam percorridos manualmente, o que comparado com o sistema automático, não é tão eficiente nem rápido de implementar.
public class Empregado implements Externalizable {
private String nome;
private int numero;
private Date data;
public Empregado(){
System.out.println("Construtor s/ argumentos");
}
public Empregado(String n, int num) {
System.out.println("Construtor c/ argumentos");
// igual ao exemplo anterior
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(nome);
out.writeInt(numero);
out.writeObject(data);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
nome = (String)in.readObject();
numero = in.readInt();
data = (Date)in.readObject();
}
}
Nos exemplos vimos que a implementação do processo ajustável e do processo manual foi igual, isto deveu-se ao facto de não termos usado, no processo ajustável, outro tipo de serialização, no entanto, no processo ajustável temos acesso ao processo automático e no processo manual não, só o ajustável nos permite combinar implementações.
-
A tecnologia Remote Method Invocation, RMI, baseia-se um pouco nesta situação. ↩