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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
Caso você não vá utilizar a Serialização, você não precisa especificar o serialVersionUID, pois este não fará diferença para você, por outro lado, se você vai utilizar a Serialização é altamente recomendável que você pense em uma estratégia para controlar essa serialização de objetos.

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.