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)