1. Por que precisamos de serialização
Imagine que seu objeto — são as coisas que você leva nas férias. A serialização é a embalagem de todo o conteúdo da mala em um contêiner especial que pode ser colocado na bagagem ou enviado pelo correio. A desserialização, por sua vez, é a abertura desse contêiner e a obtenção das coisas em seu estado original.
Em essência, a serialização transforma um objeto em um fluxo de bytes que pode ser salvo em um arquivo, enviado pela rede ou simplesmente mantido na memória. A desserialização faz o inverso: restaura o objeto a partir desse fluxo. Simplificando ao máximo, serializar é como “congelar” um objeto para depois “descongelá-lo” e obtê-lo de volta no mesmo estado.
Salvar o estado dos objetos entre as execuções do programa
Um dos cenários mais comuns é a persistência do estado do programa. Por exemplo, você tem uma lista de usuários, resultados de um jogo ou configurações de aplicativo. Tudo isso é conveniente de armazenar diretamente como objetos. Para que os dados não se percam entre execuções, eles são serializados em um arquivo e, na próxima inicialização, desserializados.
Um bom exemplo é um save de jogo comum. Quando o jogador passa de fase, seu progresso é “congelado” e gravado em um arquivo por meio da serialização. No dia seguinte, ele abre o jogo e o progresso é “descongelado”: os dados do arquivo se transformam novamente em objetos, e o jogador continua do ponto em que parou.
Vamos criar um save simples assim:
import java.io.*;
// A classe do jogador deve ser Serializable
class Player implements Serializable {
String name;
int score;
Player(String name, int score) {
this.name = name;
this.score = score;
}
}
public class GameSaveExample {
public static void main(String[] args) throws Exception {
// Criamos um objeto Player
Player player = new Player("Ihor", 1500);
// --- Salvando (serialização) ---
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("save.dat"))) {
out.writeObject(player);
System.out.println("Progresso salvo!");
}
// --- Carregando (desserialização) ---
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("save.dat"))) {
Player loaded = (Player) in.readObject();
System.out.println("Progresso carregado: " + loaded.name + " com pontos " + loaded.score);
}
}
}
Atenção: para que esse código funcione, a classe Player deve implementar a interface Serializable. Mais detalhes sobre ela — na próxima aula!
- Player é uma classe comum com os campos nome e pontos, marcada pela interface Serializable (implements Serializable).
- ObjectOutputStream grava o objeto no arquivo "save.dat".
- ObjectInputStream lê esse mesmo objeto de volta.
- Como resultado, temos um save de verdade: na próxima execução o programa carregará o objeto do jogador com o mesmo estado.
Transmissão de objetos pela rede e entre JVMs
Em sistemas distribuídos, frequentemente é necessário transmitir objetos entre diferentes programas ou até máquinas diferentes. Por exemplo, você tem um cliente e um servidor que precisam trocar mensagens. A serialização permite “empacotar” um objeto de um lado, enviá-lo pela rede e “desempacotá-lo” do outro lado.
Exemplo: o cliente envia ao servidor um objeto de pedido (Order), o servidor o recebe, desserializa e processa.
Uso em tecnologias Java
- RMI (Remote Method Invocation): permite invocar métodos de objetos remotos — a serialização é necessária para transferir argumentos e valores de retorno.
- Sessões HTTP: em servlets, objetos na sessão são serializados ao reiniciar o contêiner.
- JMS (Java Message Service): mensagens entre componentes podem ser serializadas.
- Armazenamento em cache: objetos podem ser serializados para armazenamento em cache (em disco ou em armazenamento distribuído).
Cache e portabilidade
Se você deseja salvar rapidamente resultados intermediários (por exemplo, para cache), a serialização é uma ótima ferramenta. Você serializa o objeto, o salva em disco ou na memória e, depois, o restaura rapidamente sem recomputações.
2. Exemplos de cenários de uso da serialização
Salvar uma coleção de usuários em arquivo
Suponha que você tenha a classe User:
public class User {
String name;
int age;
// ... outros campos
}
E você tem uma lista de usuários:
List<User> users = new ArrayList<>();
users.add(new User("Vasya", 25));
users.add(new User("Masha", 30));
// ... e assim por diante
Para salvar essa lista em um arquivo, você a serializa. Quando necessário — você desserializa e obtém a mesma lista com os mesmos usuários. Lembre-se de que a classe User (e todos os seus campos) deve dar suporte à serialização, isto é, implementar Serializable.
Enviar uma mensagem entre cliente e servidor
Um exemplo clássico é um chat. O usuário escreve uma mensagem, o objeto Message é serializado e enviado pela rede. O servidor recebe o fluxo de bytes, desserializa o objeto, o processa e, possivelmente, o encaminha.
import java.io.*;
import java.net.*;
// A mensagem deve ser Serializable
class Message implements Serializable {
String text;
Message(String text) {
this.text = text;
}
}
// Servidor
class Server {
public static void main(String[] args) throws Exception {
try (ServerSocket serverSocket = new ServerSocket(5000)) {
System.out.println("O servidor está aguardando conexão...");
Socket socket = serverSocket.accept();
System.out.println("Cliente conectado!");
try (ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) {
Message msg = (Message) in.readObject();
System.out.println("Mensagem recebida: " + msg.text);
}
}
}
}
// Cliente
class Client {
public static void main(String[] args) throws Exception {
try (Socket socket = new Socket("localhost", 5000)) {
try (ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream())) {
Message msg = new Message("Olá, servidor!");
out.writeObject(msg);
System.out.println("Mensagem enviada!");
}
}
}
}
Como funciona:
- Primeiro, execute o Server (ele aguarda a conexão).
- Depois, execute o Client (ele se conecta a "localhost:5000").
- O cliente serializa o objeto Message e o envia pelo socket.
- O servidor recebe o fluxo de bytes, desserializa e imprime o texto.
Aqui usamos sockets (ServerSocket, Socket) — é um mecanismo de comunicação em rede que você estudará depois. O importante agora não são os detalhes da rede, mas a própria ideia: o cliente cria um objeto Message, o serializa e envia; o servidor recebe o fluxo de bytes, desserializa de volta para um objeto e imprime a mensagem. Assim, mesmo que ainda não esteja claro o que são as classes ServerSocket e Socket, o exemplo mostra o valor da serialização: graças a ela, é possível “empacotar” um objeto, enviá-lo pela rede e, do outro lado, desempacotá-lo sem conversões extras.
Cache de objetos
Em aplicativos grandes, o cache é frequentemente usado para acelerar o funcionamento. Por exemplo, resultados de cálculos complexos são serializados e salvos no cache (arquivo, banco de dados, armazenamento distribuído). Na próxima solicitação, o resultado pode ser rapidamente restaurado desserializando o objeto.
import java.io.*;
// Resultado de cálculos que queremos armazenar em cache
class Result implements Serializable {
int value;
Result(int value) {
this.value = value;
}
}
public class CacheExample {
private static final String CACHE_FILE = "cache.dat";
public static void main(String[] args) throws Exception {
Result result;
// Verificamos se existe cache
File file = new File(CACHE_FILE);
if (file.exists()) {
// Carregamos o resultado do cache
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
result = (Result) in.readObject();
System.out.println("Carregado do cache: " + result.value);
}
} else {
// Cálculo "pesado" (no exemplo, apenas o quadrado do número)
int x = 12345;
System.out.println("Calculando... (isso é demorado)");
result = new Result(x * x);
// Salvamos o resultado no cache
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) {
out.writeObject(result);
System.out.println("Salvo no cache: " + result.value);
}
}
}
}
3. Limitações e riscos da serialização
A serialização é uma ferramenta poderosa, mas não isenta de armadilhas. Vamos discutir as principais limitações e riscos.
Nem todos os objetos podem ser serializados
Em Java, nem todos os objetos podem ser serializados “prontos para uso”. Por exemplo, objetos vinculados a recursos externos (arquivos, conexões de rede, streams de entrada/saída) não são serializáveis. Isso é lógico: não é possível serializar um “arquivo aberto” ou uma conexão de rede ativa — seu estado depende do sistema operacional e do ambiente de execução.
Exemplo: uma classe com um campo do tipo FileInputStream não pode ser serializada — ao tentar serializar ocorrerá um erro.
Questões de segurança
A serialização é uma possível brecha de segurança. Se você desserializa dados obtidos de uma fonte não confiável (por exemplo, da internet), um invasor pode enviar um fluxo de bytes malicioso que levará a um comportamento inesperado do seu programa e, às vezes, até à execução de código malicioso.
Regra: Nunca desserialize dados de fontes não confiáveis! É como aceitar um pacote de um remetente desconhecido — pode haver qualquer coisa dentro.
Compatibilidade de versões
Se você alterou a estrutura da classe (por exemplo, adicionou ou removeu um campo), objetos serializados anteriormente podem se tornar incompatíveis com a nova versão da classe. Isso pode levar a erros na desserialização. Esse assunto será abordado em mais detalhes nas próximas aulas.
Desempenho
A serialização binária em Java é bastante rápida, mas às vezes não é a mais compacta e nem sempre é conveniente para troca com outras linguagens de programação. Para integração com sistemas externos, formatos de texto (JSON, XML) são frequentemente usados.
4. Erros comuns no primeiro contato com a serialização
Erro nº 1: tentar serializar um objeto que não implementa a interface Serializable.
Como resultado, você receberá a exceção NotSerializableException. Não se esqueça de declarar explicitamente implements Serializable na classe e garantir que todos os campos também sejam serializáveis!
Erro nº 2: serializar objetos com campos não serializáveis.
Se sua classe contém um campo de um tipo que não suporta serialização (por exemplo, um stream ou uma conexão com o BD), a serialização não funcionará. A solução é marcar esses campos como transient (mais sobre isso depois).
Erro nº 3: desserializar dados de fontes não confiáveis.
Isso pode levar a vulnerabilidades de segurança ou até à execução de código malicioso. Confie apenas em dados que foram serializados pelo seu próprio programa!
Erro nº 4: alterações na estrutura da classe após a serialização.
Se você salvou um objeto e depois adicionou ou removeu um campo na classe, ao tentar desserializar surgirá um erro ou valores “estranhos”. Mais detalhes — nas próximas aulas.
GO TO FULL VERSION