CodeGym /Cursos /JAVA 25 SELF /Externalizable: ajuste fino da serialização

Externalizable: ajuste fino da serialização

JAVA 25 SELF
Nível 43 , Lição 2
Disponível

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:

  1. Implementar a interface java.io.Externalizable.
  2. 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.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION