Serialização de Objetos em Java

Introdução
Na linguagem de programação Java, a serialização de objetos é um recurso poderoso que permite transformar um objeto em um fluxo de bytes, possibilitando que ele seja facilmente armazenado em disco, transmitido pela rede ou salvo em um banco de dados. Posteriormente, esse objeto pode ser reconstruído em memória através de um processo chamado desserialização.
Esse mecanismo é amplamente utilizado em aplicações distribuídas, comunicação entre sistemas, cache de dados, persistência temporária e muito mais. Neste artigo, você vai entender os conceitos fundamentais por trás da serialização, como utilizar as classes e interfaces envolvidas e ver exemplos práticos em Java.
1 – Interface “Serializable”
Para que um objeto possa ser serializado, sua classe deve implementar a interface “java.io.Serializable”.
Essa interface é marcadora, ou seja, não possui métodos. Sua função é apenas informar à JVM que os objetos daquela classe podem ser convertidos em bytes.
Exemplo:
import java.io.Serializable;
public class Pessoa implements Serializable {
private String nome;
private int idade;
public Pessoa(String nome, int idade) {
this.nome = nome;
this.idade = idade;
}
@Override
public String toString() {
return "Pessoa{" + "nome='" + nome + '\'' + ", idade=" + idade + '}';
}
}
Se uma classe contém objetos de outras classes, essas classes também precisam implementar a classe “Serializable” para que a serialização funcione corretamente.
2 – Gravando objetos com “ObjectOutputStream”
Após implementar Serializable, podemos gravar (serializar) o objeto em um arquivo utilizando a classe ObjectOutputStream.
Essa classe transforma o objeto em bytes e o grava em um fluxo de saída, normalmente associado a um arquivo.
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializacaoExemplo {
public static void main(String[] args) {
Pessoa pessoa = new Pessoa("Maria", 30);
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("pessoa.ser"))) {
oos.writeObject(pessoa);
System.out.println("Objeto serializado com sucesso!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Após a execução, será criado um arquivo “pessoa.ser” contendo o objeto serializado em formato binário.
3 – Lendo objetos com “ObjectInputStream”
Para reconstruir o objeto (desserializar), usamos a classe ObjectInputStream. Ela lê o fluxo de bytes e recria o objeto original em memória.
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class DesserializacaoExemplo {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("pessoa.ser"))) {
Pessoa pessoa = (Pessoa) ois.readObject();
System.out.println("Objeto desserializado: " + pessoa);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
A classe “Pessoa” deve estar disponível no classpath durante a desserialização, caso contrário ocorrerá uma exceção “ClassNotFoundException”.
4 – O modificador “transient”
Nem todos os atributos devem ser serializados, por exemplo, senhas, tokens ou informações sensíveis podem ser ignoradas na serialização.
Para isso, utilizamos o modificador “transient”.
import java.io.Serializable;
public class Usuario implements Serializable {
private String nome;
private transient String senha; // não será serializada
public Usuario(String nome, String senha) {
this.nome = nome;
this.senha = senha;
}
@Override
public String toString() {
return "Usuario{" + "nome='" + nome + '\'' + ", senha='" + senha + '\'' + '}';
}
}
Durante a desserialização, o campo “senha” será reconstruído com o valor padrão do tipo (null para objetos, “0” para números, etc.).
5 – O campo “serialVersionUID”
Toda classe serializável deve definir um campo chamado “serialVersionUID”, ele é um identificador único de versão da classe.
Quando um objeto é desserializado, a JVM verifica se o “serialVersionUID” da classe atual corresponde ao da classe usada na serialização.
Se forem diferentes, ocorre uma exceção “InvalidClassException”.
private static final long serialVersionUID = 1L;
import java.io.Serializable;
public class Produto implements Serializable {
private static final long serialVersionUID = 1L;
private String nome;
private double preco;
public Produto(String nome, double preco) {
this.nome = nome;
this.preco = preco;
}
@Override
public String toString() {
return "Produto{" + "nome='" + nome + '\'' + ", preco=" + preco + '}';
}
}
Boas prática:
- Sempre declarar manualmente o “serialVersionUID”.
- Alterar o valor quando mudanças incompatíveis forem feitas na classe.
Exemplo completo:
import java.io.*;
public class ExemploCompleto {
public static void main(String[] args) {
Produto produto = new Produto("Notebook", 3999.90);
// Serialização
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("produto.ser"))) {
oos.writeObject(produto);
System.out.println("Produto serializado com sucesso!");
} catch (IOException e) {
e.printStackTrace();
}
// Desserialização
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("produto.ser"))) {
Produto produtoLido = (Produto) ois.readObject();
System.out.println("Produto desserializado: " + produtoLido);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Produto implements Serializable {
private static final long serialVersionUID = 1L;
private String nome;
private double preco;
private transient String codigoInterno = "ABC123";
public Produto(String nome, double preco) {
this.nome = nome;
this.preco = preco;
}
@Override
public String toString() {
return "Produto{" + "nome='" + nome + '\'' + ", preco=" + preco + ", codigoInterno='" + codigoInterno + '\'' + '}';
}
}
Saída:
Produto serializado com sucesso!
Produto desserializado: Produto{nome='Notebook', preco=3999.9, codigoInterno='null'}
Observe que o campo “codigoInterno”, por ser “transient”, foi ignorado na serialização.
Conclusão
A serialização de objetos em Java é um recurso essencial para persistência e comunicação entre sistemas.
Com a interface “Serializable”, as classes “ObjectOutputStream” e “ObjectInputStream”, o modificador “transient” e o campo “serialVersionUID”, você tem total controle sobre como os objetos são armazenados e reconstruídos.
Dominar esse tema é fundamental para quem deseja desenvolver aplicações robustas, distribuídas e preparadas para lidar com dados complexos de forma eficiente.