DEV Community

Java Efetivo (livro)
Java Efetivo (livro)

Posted on

Exercícios Práticos de Serialização

1.Criar uma classe simples serializável
Implemente Serializable e serialize/deserialize um objeto usando ObjectOutputStream e ObjectInputStream.

2. Testar a compatibilidade de versão
Serializar um objeto de uma versão antiga da classe e tentar desserializá-lo após uma mudança na estrutura da classe.
Testar o comportamento do serialVersionUID.

3. Criar uma forma serializada personalizada
Usar writeObject e readObject para definir manualmente a serialização de uma classe.

4. Explorar riscos de segurança
Criar um objeto vulnerável a ataques via serialização (exemplo: permitir modificação de campos privados via desserialização).
Corrigir a vulnerabilidade implementando readObject.

5. Comparar serialização com outras abordagens
Implementar persistência de um objeto com JSON (Gson, Jackson) e comparar com a serialização Java.


1. Faça uma classe serializável e demonstre a serialização e desserialização de um objeto dessa classe.

import java.io.*;

// Classe serializável
class Pessoa implements Serializable {
    private static final long serialVersionUID = 1L; // UID para manter compatibilidade
    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 + "}";
    }
}

public class SerializacaoExemplo {
    public static void main(String[] args) {
        Pessoa pessoa = new Pessoa("João", 30);

        // Serializar objeto
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("pessoa.ser"))) {
            out.writeObject(pessoa);
            System.out.println("Objeto serializado com sucesso!");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Desserializar objeto
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("pessoa.ser"))) {
            Pessoa pessoaLida = (Pessoa) in.readObject();
            System.out.println("Objeto desserializado: " + pessoaLida);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

📌 Explicação: Esse código cria uma classe Pessoa serializável, salva um objeto em um arquivo e depois o lê de volta.

2. Modifique a classe do exercício anterior para incluir um novo campo e demonstre o problema de compatibilidade ao desserializar um objeto antigo.

import java.io.*;

class Pessoa implements Serializable {
    private static final long serialVersionUID = 1L; // UID fixo para manter compatibilidade
    private String nome;
    private int idade;
    private String email; // Novo campo adicionado

    public Pessoa(String nome, int idade, String email) {
        this.nome = nome;
        this.idade = idade;
        this.email = email;
    }

    @Override
    public String toString() {
        return "Pessoa{nome='" + nome + "', idade=" + idade + ", email='" + email + "'}";
    }
}

public class SerializacaoProblema {
    public static void main(String[] args) {
        // Tentativa de desserializar um objeto criado antes da adição do campo 'email'
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("pessoa.ser"))) {
            Pessoa pessoaLida = (Pessoa) in.readObject();
            System.out.println("Objeto desserializado: " + pessoaLida);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

📌 Explicação: Se um objeto da versão anterior (sem email) for desserializado, pode causar InvalidClassException, pois o serialVersionUID e a estrutura da classe mudaram.

3. Corrija o problema do exercício anterior implementando uma forma serializada compatível usando serialVersionUID.

import java.io.*;

class Pessoa implements Serializable {
    private static final long serialVersionUID = 1L;
    private String nome;
    private int idade;
    private transient String email; // Campo transient não será serializado

    public Pessoa(String nome, int idade, String email) {
        this.nome = nome;
        this.idade = idade;
        this.email = email;
    }

    @Override
    public String toString() {
        return "Pessoa{nome='" + nome + "', idade=" + idade + "', email='" + (email != null ? email : "N/A") + "'}";
    }
}

public class SerializacaoCorrigida {
    public static void main(String[] args) {
        // Desserializar objeto antigo sem email
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("pessoa.ser"))) {
            Pessoa pessoaLida = (Pessoa) in.readObject();
            System.out.println("Objeto desserializado: " + pessoaLida);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

📌 Explicação: O campo email foi marcado como transient, o que impede sua serialização e mantém a compatibilidade.

4. Demonstre como a serialização pode ser uma falha de segurança permitindo a modificação de um objeto privado.

import java.io.*;

class Usuario implements Serializable {
    private static final long serialVersionUID = 1L;
    private String nome;
    private String senha; // Senha não protegida!

    public Usuario(String nome, String senha) {
        this.nome = nome;
        this.senha = senha;
    }

    @Override
    public String toString() {
        return "Usuario{nome='" + nome + "', senha='" + senha + "'}";
    }
}

public class FalhaSeguranca {
    public static void main(String[] args) {
        Usuario usuario = new Usuario("admin", "1234");

        // Serializar usuário
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("usuario.ser"))) {
            out.writeObject(usuario);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Modificar o arquivo manualmente e desserializar (Simulando um ataque)
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("usuario.ser"))) {
            Usuario usuarioHackeado = (Usuario) in.readObject();
            System.out.println("Usuário comprometido: " + usuarioHackeado);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

📌 Explicação: A senha é armazenada em texto plano e pode ser extraída ou modificada por um invasor ao alterar o arquivo serializado.

5. Corrija o problema de segurança do exercício anterior tornando os campos sensíveis transient e implementando writeObject() e readObject().

import java.io.*;

class UsuarioSeguro implements Serializable {
    private static final long serialVersionUID = 1L;
    private String nome;
    private transient String senha; // Agora a senha não será serializada

    public UsuarioSeguro(String nome, String senha) {
        this.nome = nome;
        this.senha = senha;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeObject(encrypt(senha)); // Salva senha criptografada
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        senha = decrypt((String) in.readObject()); // Recupera senha descriptografada
    }

    private String encrypt(String senha) {
        return senha == null ? null : new StringBuilder(senha).reverse().toString(); // Simples inversão
    }

    private String decrypt(String senhaCriptografada) {
        return senhaCriptografada == null ? null : new StringBuilder(senhaCriptografada).reverse().toString();
    }

    @Override
    public String toString() {
        return "UsuarioSeguro{nome='" + nome + "', senha='" + senha + "'}";
    }
}

public class SegurancaCorrigida {
    public static void main(String[] args) {
        UsuarioSeguro usuario = new UsuarioSeguro("admin", "1234");

        // Serializar
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("usuarioSeguro.ser"))) {
            out.writeObject(usuario);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Desserializar
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("usuarioSeguro.ser"))) {
            UsuarioSeguro usuarioLido = (UsuarioSeguro) in.readObject();
            System.out.println("Usuário seguro: " + usuarioLido);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

📌 Explicação: O campo senha agora é transient, e os métodos writeObject() e readObject() aplicam uma criptografia simples.

5. Comparar serialização com outras abordagens

5.1 Persistência usando Serialização Java

import java.io.*;

// Classe serializável
class Pessoa implements Serializable {
    private static final long serialVersionUID = 1L;
    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 + "}";
    }
}

public class SerializacaoJava {
    public static void main(String[] args) {
        Pessoa pessoa = new Pessoa("João", 30);

        // Serializar em arquivo
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("pessoa.ser"))) {
            out.writeObject(pessoa);
            System.out.println("Objeto serializado: " + pessoa);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Desserializar do arquivo
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("pessoa.ser"))) {
            Pessoa pessoaLida = (Pessoa) in.readObject();
            System.out.println("Objeto desserializado: " + pessoaLida);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

📌 Pontos positivos: Integrado ao Java, eficiente em binário.
📌 Pontos negativos: Problemas de compatibilidade ao mudar a classe, não legível para humanos.

5.2 Persistência usando JSON com Gson

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.io.*;

// Classe normal sem Serializable
class PessoaGson {
    private String nome;
    private int idade;

    public PessoaGson(String nome, int idade) {
        this.nome = nome;
        this.idade = idade;
    }

    @Override
    public String toString() {
        return "PessoaGson{nome='" + nome + "', idade=" + idade + "}";
    }
}

public class PersistenciaGson {
    public static void main(String[] args) {
        PessoaGson pessoa = new PessoaGson("João", 30);
        Gson gson = new GsonBuilder().setPrettyPrinting().create();

        // Serializar em JSON
        try (FileWriter writer = new FileWriter("pessoa.json")) {
            gson.toJson(pessoa, writer);
            System.out.println("Objeto serializado em JSON:\n" + gson.toJson(pessoa));
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Desserializar do JSON
        try (Reader reader = new FileReader("pessoa.json")) {
            PessoaGson pessoaLida = gson.fromJson(reader, PessoaGson.class);
            System.out.println("Objeto desserializado: " + pessoaLida);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

📌 Pontos positivos: Legível, fácil de integrar com APIs e persistência de dados.
📌 Pontos negativos: JSON ocupa mais espaço e é um pouco mais lento que binário.

5.3 Persistência usando JSON com Jackson

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.*;

// Classe normal sem Serializable
class PessoaJackson {
    private String nome;
    private int idade;

    public PessoaJackson() {} // Jackson precisa de um construtor vazio

    public PessoaJackson(String nome, int idade) {
        this.nome = nome;
        this.idade = idade;
    }

    @Override
    public String toString() {
        return "PessoaJackson{nome='" + nome + "', idade=" + idade + "}";
    }
}

public class PersistenciaJackson {
    public static void main(String[] args) {
        PessoaJackson pessoa = new PessoaJackson("João", 30);
        ObjectMapper objectMapper = new ObjectMapper();

        // Serializar em JSON
        try {
            objectMapper.writeValue(new File("pessoa_jackson.json"), pessoa);
            System.out.println("Objeto serializado em JSON com Jackson");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Desserializar do JSON
        try {
            PessoaJackson pessoaLida = objectMapper.readValue(new File("pessoa_jackson.json"), PessoaJackson.class);
            System.out.println("Objeto desserializado: " + pessoaLida);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

📌 Pontos positivos: Alto desempenho, flexível, suporta diversas configurações.
📌 Pontos negativos: Requer bibliotecas externas, pode exigir configurações extras.

5.4 Comparação das abordagens

Image description

📌 Conclusão:

  • A serialização Java é eficiente, mas tem problemas de compatibilidade e não é legível.
  • O Gson é simples, fácil de usar e ideal para APIs.
  • O Jackson tem melhor desempenho e é mais configurável, sendo ótimo para grandes aplicações.

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay