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.