O processo de serialização além realizar o principio básico de sua característica, que é realizar a serialização e deserialização de um objeto, realiza ainda o controle de versionamento de determinado objeto, para determinar assim a compatibilidade entre objeto e classe, e assim evitar inconsistências na recuperação do estado anteriormente salvo do objeto em questão.
O controle de versionamento é feito através de um atributo chamado serialVersionUID, ele identifica se a versão do objeto é compatível com a versão da classe que serializou este. Quando dizemos compatível, é porque a classe que serializou o objeto não precisa ser exatamente e metodicamente igual ao objeto serializado, isso porque sabemos que a serialização não “se importa” com métodos, apenas com o valor “puro do objeto”.
Usando o serialVersionUID em Java
Este identificador não é um número aleatório, como muitos pensam. Ele é baseado em uma codificação hash que identifica todo o conteúdo daquela determinada classe, assim quando qualquer conteúdo mudar na classe, o serialVersionUID também muda. É importante salientar que quando não é especificado nenhum serialVersionUID explicitamente, a própria JVM irá gerar este identificador.
Antes de darmos inicio aos exemplos, tenha em mente que existem 2 formas de gerar o serialVersionUID para sua classe, através da ferramenta serialver ou através do próprio Eclipse. Para utilizar a serialver basta digitar “serialver nomedaclasse” no prompt de comando para que seja criado um serialVersionUID para aquela determinada classe, por outro lado com o Eclipse é mais simples ainda, visto que você só precisa pedir para ele gerar o serialVersionUID, quando aparecer um WARNING na IDE.
Vamos ver na listagem 1 um Bean com muitas propriedades e seu respectivo serialVersionUID, assim você perceberá que independente da quantidade de atributos ou conteúdo da sua classe, o tamanho do identificador serial será sempre o mesmo.
Listagem 1: Bean com serialVersionUID
import java.io.Serializable;
import java.util.List;
import java.util.Set;
public class Pessoa implements Serializable {
private static final long serialVersionUID = 9125277018717732648L;
private Integer id;
private String nome;
private String endereco;
private String telefone;
private String email;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getEndereco() {
return endereco;
}
public void setEndereco(String endereco) {
this.endereco = endereco;
}
public String getTelefone() {
return telefone;
}
public void setTelefone(String telefone) {
this.telefone = telefone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
Nossa classe Pessoa tem a seguinte versão: 9125277018717732648L. Agora vamos alterar o método getEmail para ver o que acontece com o versionamento da nossa classe.
Listagem 2: Alterando getEmail e gerando novo serialVersionUID
import java.io.Serializable;
import java.util.List;
import java.util.Set;
public class Pessoa implements Serializable {
private static final long serialVersionUID = 9125277018717732648L;
private Integer id;
private String nome;
private String endereco;
private String telefone;
private String email;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getEndereco() {
return endereco;
}
public void setEndereco(String endereco) {
this.endereco = endereco;
}
public String getTelefone() {
return telefone;
}
public void setTelefone(String telefone) {
this.telefone = telefone;
}
public String getEmail() {
return email.trim();
}
public void setEmail(String email) {
this.email = email;
}
}
Adicionamos um trim() ao nosso e-mail, mas o serialVersionUID continua idêntico, isso porque a serialização ignora os métodos. Agora, vamos adicionar um atributo a nossa classe:
Listagem 3: Adicionando um atributo ao nosso bean Pessoa
import java.io.Serializable;
import java.util.List;
import java.util.Set;
public class Pessoa implements Serializable {
private static final long serialVersionUID = -1235651048341475205L;
private String novoAtributoQualquer;
private Integer id;
private String nome;
private String endereco;
private String telefone;
private String email;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getEndereco() {
return endereco;
}
public void setEndereco(String endereco) {
this.endereco = endereco;
}
public String getTelefone() {
return telefone;
}
public void setTelefone(String telefone) {
this.telefone = telefone;
}
public String getEmail() {
return email.trim();
}
public void setEmail(String email) {
this.email = email;
}
}
Nosso serialVersionUID mudou como esperado. E qual o impacto disto na nossa aplicação ?
Quando um objeto é serializado com uma versão X e quando a deserialização é feita com uma classe que já possui versão X+1, é lançada uma exception java.io.InvalidClassException. Nos casos acima veja o que pode ocorrer:
Serializamos a classe da listagem 2 que possui versão 9125277018717732648L, depois adicionamos o novo atributo e transformamos a listagem 2 na listagem 3. Agora precisamos recapturar/deserializar aquele objeto que foi serializado na listagem 2, porém o compilador vai identificar que a listagem 2 e a listagem 3 possuem versões distintas e lançará um InvalidClass Exception. Isso é ideal em alguns casos onde o cliente deve de fato atualizar a sua aplicação para compatibilizar a mesma com o que está em desenvolvimento, porém em alguns casos esse tipo de alteração não irá implicar em nada e gostaríamos que o objeto fosse deserializado mesmo que não tivesse todos os atributos que queremos, então como fazer isso ? A resposta é simples: Basta mudar manualmente o serialVersionUID para a versão anterior a serialização, em nosso caso mudariamos de “- 1235651048341475205L” para “ 9125277018717732648L” e nosso problema de InvalidClassException estaria resolvido.
Imagine a situação onde precisamos manter esse controle de versionamento dos Beans para garantir que a aplicação do cliente está atualizada conforme os Beans presentes no servidor. Uma aplicação tipicamente MVC (Model View Controller).
Caso você esteja trabalhando com uma classe que acabou de ser criada, e que você tem certeza que ainda não há nenhum objeto serializado para ela, você poderá facilmente trabalhar com o serialVersionUID = 1L.
Regras Importantes
No escopo deste assunto de serialização há diversos tópicos, vamos ainda abordas aqui algumas regras importantes que devem ser lembradas ao aplicar a serialização:
- Caso o arquivo que será utilizado para deserializar determinado objeto não exista, acontece aqui a serialização, mas mesmo assim você verá uma exceção pelo fato de o arquivo não existir.
- Se você tiver uma Superclasse que implemente Serializable então automaticamente, todas as suas subclasses estarão aptas a serem serializadas. É por isso que muita das vezes criamos um Bean Genérico com comportamentos padrões, assim não precisamos ficar repetindo “implements Serializable” em todos os Beans.
- Quando uma Superclasse não implementa Serializable mas sua subclasse implementa, ao deserializar um objeto da subclasse, o construtor da Superclasse é chamado. Isso é importante ao ponto que seus valores podem ficar nulos ou vazios sem que você saiba onde está o erro.
- Se você possuir uma Classe que implementa Serializable e esta fizer referência a uma outra classe que não implementa Serializable, você receberá uma exception. Em outras palavras, classes Serializable devem se comunicar com outras classes Serializable.
- Variáveis statics não são serializadas por um motivo bem simples: Estas não estão para o objeto, mas sim para a classe como um todo.
CONCLUSÃO
Este artigo teve como principal objetivo demonstrar o uso do identificador de versionamento serialVersionUID, que é um dos recursos oferecidos pela Serialização em Java. Este é indispensável caso você vá de fato fazer uso da Serialização, caso contrário o uso deste identificador é dispensável e você pode deixar esse trabalho a cargo da JVM que gerará automaticamente este valor para você. A melhor forma de treinar esse tipo de conceito é na prática, sem dúvida, pois apenas com erros e acertos você entenderá com perfeição a real funcionalidade do serialVersionUID, e até mesmo da Serialização como um todo.