Ir para o conteúdo

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.


  1. A tecnologia Remote Method Invocation, RMI, baseia-se um pouco nesta situação.