1. Introdução
Em Java, para serializar objetos usa-se com mais frequência a interface Serializable. Ela é simples: basta implementar a interface e o objeto pode ser gravado/lido com ObjectOutputStream/ObjectInputStream. Mas às vezes isso não é suficiente:
- É preciso controlar completamente quais campos e como serão serializados.
- É necessário garantir compatibilidade entre diferentes versões da classe.
- É importante reduzir o tamanho do arquivo serializado ou acelerar o processo.
Para esses casos, Java oferece a interface Externalizable — uma forma mais “manual” e flexível de serialização.
Em resumo:
- Serializable — serialização automática: o Java decide o que e como escrever.
- Externalizable — serialização manual: você mesmo indica o que e como salvar/restaurar.
2. Contrato Externalizable: implementando writeExternal e readExternal
Para usar Externalizable, é preciso:
- Implementar a interface java.io.Externalizable.
- Implementar obrigatoriamente dois métodos:
- void writeExternal(ObjectOutput out) throws IOException
- void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
Exemplo:
import java.io.*;
public class User implements Externalizable {
private String name;
private int age;
// Construtor público sem parâmetros obrigatório!
public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = in.readUTF();
age = in.readInt();
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
Importante: o desenvolvedor decide quais campos serão serializados e em qual ordem. Contudo, há um requisito obrigatório: a classe deve ter um construtor public sem parâmetros. Se ele não existir, durante a desserialização o programa lançará InvalidClassException.
3. Quando usar Externalizable?
Use Externalizable se:
- For necessário controle total sobre o formato dos dados. Por exemplo, você quer serializar apenas parte dos campos, ou serializá-los em uma ordem/formato específico.
- O objetivo for otimizar desempenho e tamanho do arquivo. A serialização padrão adiciona informações de serviço (metadados, nomes de classes, tipos etc.). Com Externalizable, você grava apenas os dados necessários.
- For preciso garantir compatibilidade com versões anteriores. Se a estrutura da classe mudar, é possível implementar manualmente a lógica de leitura de versões antigas e novas dos dados.
- Houver objetos não padronizados para serializar. Por exemplo, se você tiver campos que não podem ser serializados da forma padrão (como transient, volatile ou estruturas complexas).
Quando NÃO usar?
- Se você não precisa de controle total — use Serializable, é mais simples e seguro.
- Se não tem certeza de que conseguirá manter a compatibilidade do formato dos dados ao alterar a classe.
4. Vantagens e desvantagens do Externalizable em relação ao Serializable
Vantagens:
- Controle total sobre a serialização. Você decide o que e como escrever/ler.
- Compacidade. Sem metadados desnecessários — apenas os seus dados.
- Velocidade. Menos dados — gravação/leitura mais rápidas.
- Flexibilidade. Dá para implementar suporte a diferentes versões de formato, adicionar compressão, criptografia etc.
Desvantagens:
- Implementação manual — é fácil cometer erros. Se você inverter a ordem de escrita/leitura, a serialização “quebra” (gera erro ou dados incorretos).
- Sem suporte automático a transient, serialVersionUID. Tudo precisa ser pensado e implementado manualmente.
- Manutenção mais difícil. Ao alterar a estrutura da classe, é preciso lembrar de atualizar os métodos de serialização.
- Construtor público sem parâmetros é obrigatório.
- Menos “mágica” — mais responsabilidade.
5. Exemplos: serialização e desserialização de um objeto simples
Serialização do objeto
User user = new User("Alice", 30);
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.bin"))) {
out.writeObject(user);
}
Desserialização do objeto
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.bin"))) {
User loaded = (User) in.readObject();
System.out.println(loaded); // Alice (30)
}
Atenção: se você mudar a ordem de escrita/leitura dos campos ou esquecer de serializar algum campo, os dados ficarão incorretos! Os métodos writeExternal e readExternal devem estar estritamente alinhados na sequência de operações.
Exemplo: serializar apenas parte dos campos
public class SecretUser implements Externalizable {
private String login;
private transient String password; // transient não tem efeito para Externalizable
public SecretUser() {}
public SecretUser(String login, String password) {
this.login = login;
this.password = password;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(login);
// Não serializamos a senha!
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
login = in.readUTF();
password = null; // não restauramos a senha
}
}
6. Prática: comparando o tamanho do arquivo serializado
Vamos comparar quanto “pesam” os arquivos serializados via Serializable e via Externalizable.
Classe com Serializable
public class UserSerializable implements Serializable {
private String name;
private int age;
public UserSerializable(String name, int age) {
this.name = name;
this.age = age;
}
}
Classe com Externalizable
public class UserExternalizable implements Externalizable {
private String name;
private int age;
public UserExternalizable() {}
public UserExternalizable(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = in.readUTF();
age = in.readInt();
}
}
Código para comparação
import java.io.*;
public class CompareSerialization {
public static void main(String[] args) throws Exception {
UserSerializable s = new UserSerializable("Bob", 25);
UserExternalizable e = new UserExternalizable("Bob", 25);
// Serializable
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"))) {
out.writeObject(s);
}
// Externalizable
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ext.bin"))) {
out.writeObject(e);
}
System.out.println("Serializable file size: " + new File("ser.bin").length());
System.out.println("Externalizable file size: " + new File("ext.bin").length());
}
}
Resultado:
O arquivo ser.bin (Serializable) geralmente é maior — contém informações de serviço do Java. O arquivo ext.bin (Externalizable) — apenas os seus dados, normalmente menor.
7. Erros comuns ao trabalhar com Externalizable
Erro nº 1: ausência de construtor público sem parâmetros.
A classe que implementa Externalizable deve, obrigatoriamente, ter um construtor public sem argumentos. Sem isso, a desserialização lançará InvalidClassException.
Erro nº 2: violação da ordem de escrita e leitura dos campos.
Os métodos writeExternal e readExternal devem operar na mesma ordem. Se ao escrever você salvar primeiro o campo name e, ao ler, tentar primeiro ler age, os dados serão corrompidos.
Erro nº 3: campos omitidos na serialização.
Se esquecer de gravar um campo em writeExternal, na desserialização ele terá o valor null (para tipos por referência) ou 0 (para numéricos).
Erro nº 4: uso incorreto de transient ou serialVersionUID.
Diferentemente de Serializable, em Externalizable esses mecanismos não funcionam automaticamente — você precisa controlar por conta própria quais campos salvar e quais não.
Erro nº 5: alterar a estrutura da classe sem atualizar os métodos.
Se você adicionar ou remover campos e não fizer as alterações correspondentes em writeExternal e readExternal, os dados antigos salvos podem deixar de carregar corretamente.
GO TO FULL VERSION