20 julho 2009

SCJP - 06 - Serialização

Fazia bastante tempo que eu não lia um capítulo da SCJP 6, mas como eu preciso retomar meus estudos, então, por tabela, tammbém preciso retomar minhas anotações/posts sobre o que eu andar lendo...



Em Ciência da Computação, no contexto de armazenamento e transmissão de dados, a serialização é o processo de salvar um objeto em um meio de armazenamento (como um arquivo de computador ou um buffer de memória) ou transmiti-lo por uma conexão de rede, seja em forma binária ou em formato de texto como o XML. Esta série de bytes pode ser usada para recriar um objeto com o mesmo estado interno que o original. [Wikipedia]

Você, estagiário (não tenho saudades dessa época), vai se lembrar de serialização quando estiver desenvolvendo alguma aplicação web, por exemplo. Eu nunca havia feito serialização manualmente até o momento em que eu li essa seção do livro da SCJP 6.

import java.util.*;
import java.io.*;

class Pessoa implements Serializable { 
    private String nome;
    
    public Pessoa (String nome){
        this.nome = nome;
    }
    
    public String getNome() {
        return this.nome;
    }
    
    public void setNome(String nome){
        this.nome = nome;
    }
 
    public String toString() {
        String result = "nome: " + this.nome;
        return result;
    }
}

public class Principal {
    public static void main(String[] args){
        Pessoa pessoa = new Pessoa("Leandro");

        String fileName = Pessoa.class.getName() + ".txt";
        System.out.println("Antes da serialização -  " 
            + pessoa);
        serializeObject(fileName, pessoa);
        
        Pessoa pessoaNova = 
            (Pessoa) deserializeObject(fileName);
        System.out.println("Depois da serialização - " 
            + pessoaNova);
    }
 
    public static void serializeObject(
        String fileName, 
        Object object){
        try {
            FileOutputStream fos = 
                new FileOutputStream(fileName);
            ObjectOutputStream oos = 
                new ObjectOutputStream(fos);
            oos.writeObject(object);
            oos.close();
        } catch (Exception ex){
            ex.printStackTrace();
        }
    }
 
    public static Object deserializeObject(String fileName){
        Object object = null;
        try {
            FileInputStream fis = 
                new FileInputStream(fileName);
            ObjectInputStream ois = 
                new ObjectInputStream(fis);
            object = ois.readObject();
            ois.close();
        } catch (Exception ex){
            ex.printStackTrace();
        }
         
        return object;
    }
}

Execute esse código e abra o arquivo Pessoa.txt só por curiosidade. Note que você consegue entender parte do que foi escrito apenas por mera casualidade, pois o Java deve utilizar uma estrutura bem formada para organizar os dados de um objeto num arquivo.



Agora, vamos complicar. E se eu quiser que uma das variáveis de instância da classe Pessoa seja uma referência a outro objeto? Insira no código acima a classe Contato:

class Contato {
    private String telefone;
    
    Contato(String telefone){
        this.telefone = telefone;
    }

    public String getTelefone(){
        return this.telefone;
    }
  
    public void setTelefone(String telefone){
        this.telefone = telefone;
    }

    public String toString() {
        String result = "telefone: " + telefone;
        return result;
    }
}

Modifique a classe Pessoa:
class Pessoa implements Serializable { 
    private String nome;
    private int idade;
    private Contato dadosContato;
    
    Pessoa (
            String nome, 
            int idade, 
            Contato dadosContato){
 
        this.nome = nome;    
        this.idade = idade;
        this.dadosContato = dadosContato
    }

    ...

    public int getIdade() {
        return this.idade;
    }
 
    public void setIdade(int idade){
        this.idade = idade;
    }
  
    public Contato getDadosContato() {
        return this.dadosContato;
    }
 
    public void setDadosContato(Contato dadosContato){
        this.dadosContato = dadosContato;
    }

    public String toString() {
        String result = "nome: " + this.nome + "\n";
        result += "idade: " + this.idade + "\n";
        result += "contato -  " + this.dadosContato;
        return result;
    }

}

E a classe Principal também:
public class Principal {
    public static void main(String[] args){
        Contato dadosContato = new Contato("9876-5432");
        Pessoa pessoa = 
            new Pessoa("Leandro",23, dadosContato);        
    }

    ...
}

Agora tente executar novamente. Conseguiu? Eu acho que não...


Como você pode ver, o programa não consegue serializar a propriedade dadosContrato da classe Pessoa, porque a classe Contrato não é serializavel. Você conseguiria resolver esse problema, fazendo a classe Contrato implementar Serializable, mas e se não tiver acesso ao seu código fonte?

Incluindo o modificador de acesso transient à propriedade dadosContrato de Pessoa, você não salva o seu estado quando serializar a instância de Pessoa, acabando com o erro em tempo de execução. Entretanto, vale lembrar que nenhum construtor da classe a ser serializada é chamado, muito menos os valores atribuidos em tempo de declaração das variáveis de instância são usados, o que significa que as variáveis transientes são inicializadas com valores default.

Para testar isso, tente serializar a classe abaixo:

class Teste implements Serializable{
    private transient int numero = 42;
    private String texto;
    
    Teste() {
        this.numero = 25;
        this.texto = "Mais um teste";
    }

    Teste(String texto, int numero){
        this.texto = texto;
        this.numero = numero;
    }
    
    public String getTexto() {
  return this.texto;
 }
 
    public void setTexto(String texto){
        this.texto = texto;
    }
  
    public int getNumero() {
        return this.numero;
    }
    
    public void setNumero(int numero){
        this.numero = numero;
    }
    
    public String toString(){
        String result = "texto: " + this.texto + "\n";        
        result += "numero: " + this.numero;
        return result;
    }
}



Mas e se for justamente salvar esta propriedade não serializável o que você precisa?

Você pode, além de fazer esta propriedade virar transient, implementar um par de métodos na sua classe Pessoa, para serializar você mesmo os dados de Contato durante a serialização de Pessoa. Altere a classe Pessoa conforme código abaixo:

class Pessoa implements Serializable { 
    private String nome;
    private int idade;
    private transient Contato dadosContato;

    ...

    private void writeObject(ObjectOutputStream os) {
        try {
            os.defaultWriteObject();
            Boolean checkDadosContrato = 
                (this.dadosContato != null);
            os.writeBoolean(checkDadosContrato);
            if (checkDadosContrato) {
                os.writeObject(this.dadosContato
                    .getTelefone());
            } 
        } catch (Exception ex){
            ex.printStackTrace();
        }
    }
    
    private void readObject(ObjectInputStream is) {
        try {
            is.defaultReadObject();
            Boolean checkDadosContrato = 
                is.readBoolean();      
            if (checkDadosContrato) {
                String telefone = (String) is.readObject();
                this.dadosContato = new Contato(telefone);
            } 
        } catch (Exception ex){
            ex.printStackTrace();
        }
    }

Acho que agora você consegue executar o programa.


Tá quase acabando, prometo :D Vimos que nenhum construtor da classe serializavel é executado. Vimos que variaveis transientes não são salvas. Vimos como serializar uma propriedade não serializável. Mas e quando a super classe não for serializável? E quanto às variáveis estáticas?

Bem, variáveis estáticas não correspondemm ao estado do objeto (instância da classe) em si. Perceba que todas as instâncias de uma classe compartilham o mesmo valor de uma variável estatica. Que sentido faria salvá-la? E se duas ou mais instâncias de uma classe fossem salvas, qual valor para a variável estática seria utilizado?

Quanto à super classe não serializável... Bem, dissemos que o construtor da classe serializável não é chamado, nem as variáveis de instância são inicializadas com os valores descritos em tempo de declaração. Mas o construtor da primeira classe não serializável é chamado, que chama o construtor da sua super classe, e assim por diante. Quer tirar a dúvida?

class TesteBase {
    private long numeroLongo = 100000L;
    private String textoBase;
    
    TesteBase() {
        textoBase = "texto base";
    }
    
    TesteBase(long numeroLongo, String textoBase){
        this.numeroLongo = numeroLongo;
        this.textoBase = textoBase;
    }
    
    public String toString(){
        String result = 
            "textoBase: " + this.textoBase + "\n";        
        result += "numeroLongo: " + this.numeroLongo;
        return result;
    }
}

class Teste extends TesteBase implements Serializable{
    private transient int numero = 42;
    private String texto;
    
    Teste() {
        this.numero = 25;
        this.texto = "Mais um teste";
    }
    
    Teste(
        String texto, 
        int numero, 
        long numeroLongo, 
        String textoBase){
        
        super(numeroLongo,textoBase);
        
        this.texto = texto;
        this.numero = numero;
    }
    
    public String getTexto() {
        return this.texto;
    }
    
    public void setTexto(String texto){
        this.texto = texto;
    }
    
    public int getNumero() {
        return this.numero;
    }
    
    public void setNumero(int numero){
        this.numero = numero;
    }
    
    public String toString(){
        String result = 
            "texto: " + this.texto + "\n";        
        result += "numero: " + this.numero + "\n";
        result += super.toString();
        return result;
    }
}





Creative Commons License
Esta obra está licenciada sob uma Licença Creative Commons.
Comentários
0 Comentários

0 comments:

Postar um comentário

Regras são chatas, mas...

- Seu comentário precisa ter relação com o assunto do post;
- Em hipótese alguma faça propaganda de outros blogs ou sites;
- Não inclua links desnecessários no conteúdo do seu comentário;
- Se quiser deixar sua URL, comente usando a opção OpenID;
- CAIXA ALTA, miguxês ou erros de ortografia não serão tolerados;
- Ofensas pessoais, ameaças e xingamentos não são permitidos;