Entendendo serialização de objetos e o serialVersionUID

Por Carlos Alberto Silva
Publicado en Agosto 2017

Motivação

No processo de serialização de objetos um elemento que geralmente é sempre um quebra-cabeça para muitos desenvolvedores Java e frequentemente acaba provocando várias dúvidas é o serialVersionUID. São recorrentes questionamentos do tipo: O que é serialVersionUID? Como ele é gerado? Como devo utilizá-lo? Posso incorrer em algum problema se não o utilizar? De que forma ele é usado? Ao longo desse artigo procuraremos trabalhar vários pontos no sentido de dirimir essas dúvidas e ajudar os profissionais que militam no universo Java.

Serialização em Java

Primeiramente antes de entrar a fundo nessas questões, vamos entender no que consiste a serialização de um objeto em Java e algumas características deste processo. A serialização é quando um objeto é transformado, em umacadeia de bytes e desta forma pode ser manipulado de maneira mais fácil, seja através de transporte pela rede ou salvo no disco. Após a transmissão ou o armazenamento esta cadeia de bytes pode ser transformada novamente no objeto Java que o originou. Em suma, a ideia por trás da serialização é a de "congelar" o objeto, guardando-o por um tempo indeterminado, movê-lo e depois "descongelar" esseobjeto tornando novamente utilizável.Cenários comuns para o uso da serialização de objetos dentro do Java, são as invocações remotas de métodos através de objetos distribuídos, aplicações que fazem uso do mapeamento objeto/relacional e servidores de aplicações quando um cliente está desatualizado em relação a versão dos jars necessários.

A serialização trabalha apenas com atributos de instância de uma classe, não incluindo os atributos estáticos. Outro detalhe importante da serialização é que se o objeto a ser serializado for proveniente de uma subclasse, todos os atributos de instancia, mesmos aqueles origináriosde superclasses, serão serializados. Também se um objeto contiver referências de outros objetos, todas as referências serão serializadas.

Para um objeto estar credenciado a passar pelo processo de serialização sua classe deve implementar a interface java.io.Serializable que sinalizará a máquina virtual Java (JVM) que objetos daquela classe estão aptos a serem serializadas.Caso não se deseje serializar algum atributo de instância específico de um determinado objeto, basta sinalizá-lo como transient, assim o objeto serializado não conterá a informação referente a este atributo.

Percebidaa importância da serialização e seus principais conceitos,vamos passar a entender a função da propriedade serialVersionUID dentro desse contexto.

O que é serialVersionUID?

SerialVersionUID é um número queidentificaa versão da classe que foi usada durante o processo de serialização. Esse valor é utilizado para rastrear a compatibilidade de versões serializadas das classes e saber se o objeto que se está recuperando é de uma versão “compatível” com a versão da classe que foi utilizada na origem paraserializar o objeto: em outras palavras, os arquivos .class gerados a partir da compilação da classe não precisam ser necessariamente os mesmos para que o processo de serialização ocorra com sucesso. O objetivo da presença desse atributo é identificar a versão da classe que foi criada durante o processo de serializaçãodo objeto.

Existem duas formas para se gerar esse número: a implícita e a explicita. Essa é realizada pelo desenvolvedor via código. Já a primeira é realizada pelaJVM no momento da compilação. Porém, essa não é uma boa ação e veremos adiante o porquê. Antes veremos como o Java gera automaticamente esse valor.

Como o serialVersionUID é gerado?

O Java fará implicitamente essa definição no momento em que a classe for compilada. Alguns quesitos são levados em conta para formar esse valor, sendo eles o nome da classe, das superclasses, o nome dos seus atributos e seus modificadores, o nome das interfaces que a classe implementa e algumas outras coisas que podem ser vistas na documentação.

Esses elementos são agrupados e em função deles é aplicado um algoritmo de hash que dará origem aoserial. Portanto, esse valor não é aleatório e qualquer alteração em algum desses elementos gerará um valor diferente para a versão. Mesmo a implementação do compilador pode provocar valores diferentes.

Dessa forma, se o serialVersionUID utilizado durante a serialização não bater exatamente com o serialVersionUID da classe que está sendo usada para recuperar o objeto, uma exceptiondo tipo java.io.InvalidClassException élançada.

Como descobrir o valor gerado pelo JDK?

A ferramenta serialver presente no diretório bin da pasta JAVA_HOME, ajuda a descobrir o serialVersionUID de uma classe.

Para demostrar o funcionamento desta ferramenta, criaremos a classe Vinho, conforme a Listagem 1.




01	import java.io.Serializable;
02
03 	public class Vinho implements Serializable {
04  	private String nome;
05  	private String tipo;
06			//gets e sets omitidos
07	}


Listagem 1. Primeira versão daClasse Vinho.

Oprimeiro passo é compilar a classe e gerar um arquivo .class. Em seguida, no terminal do sistema operacional, é invocadoo comando serialver [classname] que mostra o valor gerado conforme Figura 1.

Figura 1 – SerialVersionUIDgerado para aprimeira versão da Classe Vinho

Como já mencionado o serialVersionUID é elaborado com base em alguns elementos, dentre eles os atributos da classe. Sabendo disso, vamos adicionar mais um atributo na classe Vinho que ficará conforme a Listagem 2 e chamarnovamente a ferramenta serialver. O resultado desta execução pode ser visto através da Figura 2.





01	import java.io.Serializable;
02
03 	public class Vinho implements Serializable {
04  	private String nome;
05    		private String tipo;
06    		private String descricao; //atributo adicionado
07 	}



Listagem 2. Segunda versão daClasse Vinho.

Figura 2 – SerialVersionUID gerado para a segunda versão da classe Vinho

Qual a importância de se gerar explicitamente?

Note que foram gerados pelo Java implicitamente (pois não houve a definição explicita) dois valores diferentes para a propriedade serialVersionUID, sinalizando versões diferentes da classe. O resultado dessa situação é que caso o programador serialize um Vinhocom a primeira classe aqui definida, e tente recuperar essa informação usando a nova versão da classe, com um atributo a mais, uma exceção será prontamente lançada.

Esse comportamento é coerente já que houveram mudanças na classe. Mas pode acontecer que a alteração feita na classe não afete as regras de negócio do sistema e que, portanto, não gere nenhum impacto. Desta forma, para não incorrer em um problema de incompatibilidade de versões é importanteque o programador declare no código o valor que ele deseja para o serialversionUID. No caso das classes que apresentamos, uma solução é utilizar o serialVersionUID gerado pela JVM na primeira versão também na segunda versão, conforme mostra a Listagem 3. Dessa forma, tornamos compatíveis as duas versões das classes.




01	import java.io.Serializable;
02
03 	public class Vinho implements Serializable {
04		private static final long serialVersionUID = 7100179587555243994L;
05  private String nome;
06  private String tipo;
07    	private String descricao;

Listagem 3. Resolvendo problema de incompatibilidade da classe Vinho.

O próprio Java já enfrentou um problema de compatibilidade que acabou provocando um bug. Isso ocorreu em função de uma leve alteração na classe java.text.AttributedCharacterIterator.Attributeda versão 1.3 para a 1.4 que acabou gerando um serialVersionUID novo. Mesmo as duas versões sendo compatíveis tecnicamente, um objeto serializado com a versão Java 1.3 não poderia ser deserializado na versão 1.4 assim como o contrário. A solução para essa situação foi descobrirem o valor do serialVersionUID da versão do Java 1.3 e declararam explicitamente o mesmo valor na versão seguinte.

Exemplo de serialização de objetos

Veremos um pequeno exemplo que ajudará a perceber na prática a questão envolvendo o serialVersionUID.

Listagem 4 mostra um exemplo de serialização de objeto. O .class utilizado para obter o objeto Vinho na linha 3 é o da Listagem 1.



01 public class Serializer {
02  public static void main(String... args) throws Exception { //imports omitidos
03    Vinhovinho = new Vinho();
04    vinho.setNome("Malbec");
05    vinho.setTipo(“Rose”);
06 
07    FileOutputStream fOut = new FileOutputStream("C:\\vinhos.ser");
08    ObjectOutputStream oOut = new ObjectOutputStream(fOut);
09    oOut.writeObject(vinho);
10    oOut.close();
11    System.out.println("Objeto serializado.");
12  }
13}

Listagem 4. Exemplo de serialização

Listagem 5 mostra a implementação de uma classe deserializadora.Caso o.class utilizado para deserializar o objetoseja o da Listagem 1 o resultado da execução será como mostra a Figura 3, demostrando sucesso pois a versão das classes utilizadas para serializar e deserializar eram as mesmas.Já se o.class utilizado para deserializar for o gerado pelo código da Listagem 2, o resultado da execução será conforme o da Figura 4, sendo uma exceção lançada.



01 public class Deserializer {
 02 public static void main(String... args) throws Exception {
 03   FileInputStream fOut= new FileInputStream("C:\\vinhos.ser");
 04   ObjectInputStream oOut= new ObjectInputStream(fOut);
 05   Vinhovinho = (Vinho) oOut.readObject();
 06   oOut.close();
 07 
 08   System.out.println("Objeto deserializado.");
 09 }

Listagem 5. Exemplo da deserialização

Figura 3 – Execução da classe deserializadora com sucesso

Figura 4 – Execução da classe deserializadora com falha

Conclusão

Neste artigo abordamos detalhes do processo de serialização de objetos em Java e trabalhamos em cima de alguns dos principais questionamentos envolvendo a propriedade serialVersionUID que afetam boa parte dos desenvolvedores. Apesar de não ser um recurso que é utilizado com frequência, é importante assimilar seu funcionamento e entender que é necessário que todas as classes que implementem a interface Serializable declarem corretamente o campo serialVersionUID, e principalmente, que a cada modificação desta classe, seu valor seja atualizadopara evitar problemas de incompatibilidade durante a deserialização de um objeto.

Carlos Alberto Silva (casilvamg@hotmail.com) é Formado em Ciência da Computação pela Universidade Federal de Uberlândia (UFU), com especialização em Desenvolvimento Java pelo Centro Universitário do Triângulo (UNITRI) e em Análise e Desenvolvimento de Sistemas Aplicados a Gestão Empresarial no Instituto Federal do Triângulo Mineiro (IFTM). Trabalha na empresa Algar Telecom como Analista de Tecnologia da Informação e atualmente é aluno do mestrado da Faculdade de Computação (FACOM) da UFU. Possui as seguintes certificações: OCJP, OCWCD e ITIL.

Este artigo foi revisto pela equipe de produtos Oracle e está em conformidade com as normas e práticas para o uso de produtos Oracle.