CodeGym /Blogue Java /Random-PT /Serialização e desserialização em Java
John Squirrels
Nível 41
San Francisco

Serialização e desserialização em Java

Publicado no grupo Random-PT
Oi! Na lição de hoje, falaremos sobre serialização e desserialização em Java. Começaremos com um exemplo simples. Imagine que você é um desenvolvedor de jogos de computador. Se você cresceu nos anos 90 e se lembra dos consoles de jogos daquela época, provavelmente sabe que eles careciam de algo que consideramos natural hoje - a capacidade de salvar e carregar jogos :) Se não, imagine isso!Serialização e desserialização em Java - 1Receio que um jogo sem essas habilidades hoje estaria condenado! De qualquer forma, o que significa 'salvar' e 'carregar' um jogo? Bem, entendemos o significado cotidiano: queremos continuar o jogo de onde paramos. Para fazer isso, criamos um certo 'ponto de verificação' que usamos posteriormente para carregar o jogo. Mas o que isso significa para um programador em vez de um jogador casual? A resposta é simples: salvamos o estado do nosso programa. Digamos que você esteja jogando contra a Espanha em um jogo de estratégia. Seu jogo tem estados: quais territórios todos possuem, quantos recursos cada um possui, quais alianças existem e com quem, quem está em guerra, e assim por diante. Esta informação, o estado do nosso programa, deve ser salva de alguma forma para restaurar os dados e continuar o jogo. Como acontece, A serialização em Java é o processo de salvar o estado de um objeto como uma sequência de bytes. A desserialização em Java é o processo de restauração de um objeto desses bytes. Qualquer objeto Java pode ser convertido em uma sequência de bytes. Por que nós precisamos disso? Dissemos repetidamente que os programas não existem por si mesmos. Na maioria das vezes, eles interagem uns com os outros, trocam dados, etc. Um formato de byte é conveniente e eficiente para isso. Por exemplo, podemos converter um objeto do nosso SavedGameclass em uma sequência de bytes, transfira esses bytes pela rede para outro computador e, em seguida, no outro computador, converta esses bytes de volta em um objeto Java! Parece difícil, né? Parece que seria difícil fazer tudo isso acontecer: / Felizmente, não é assim! :) Em Java, a interface Serializable é responsável pelo processo de serialização. Essa interface é extremamente simples: você não precisa implementar um único método para usá-la! Veja como é simples nossa classe para salvar jogos:

import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private static final long serialVersionUID = 1L;

   private String[] territoryInfo;
   private String[] resourceInfo;
   private String[] diplomacyInfo;

   public SavedGame(String[] territoryInfo, String[] resourceInfo, String[] diplomacyInfo){
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public String[] getTerritoryInfo() {
       return territoryInfo;
   }

   public void setTerritoryInfo(String[] territoryInfo) {
       this.territoryInfo = territoryInfo;
   }

   public String[] getResourceInfo() {
       return resourceInfo;
   }

   public void setResourceInfo(String[] resourceInfo) {
       this.resourceInfo = resourceInfo;
   }

   public String[] getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(String[] diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoryInfo=" + Arrays.toString(territoryInfo) +
               ", resourceInfo=" + Arrays.toString(resourceInfo) +
               ", diplomacyInfo=" + Arrays.toString(diplomacyInfo) +
               '}';
   }
}
Três arrays são responsáveis ​​por informações sobre territórios, recursos e diplomacia, e a interface Serializable diz à máquina Java: ' está tudo bem se os objetos desta classe puderem ser serializados '.' Uma interface sem uma única interface parece estranha :/ Por que é necessário? A resposta a essa pergunta é dada acima: é necessário apenas fornecer as informações necessárias para a máquina Java. Em uma lição anterior, mencionamos brevemente as interfaces de marcadores. Essas são interfaces informativas especiais que simplesmente marcam nossas classes com informações adicionais que serão úteis para a máquina Java no futuro. Eles não têm nenhum método que você precise implementar. Aqui está Serializable — uma dessas interfaces. Aqui está outro ponto importante: Por que precisamos dovariável serialVersionUID longa final estática privada que definimos na classe? Este campo contém o identificador de versão exclusivo da classe serializada. Cada classe que implementa a interface Serializable possui um identificador de versão. É determinado com base no conteúdo da classe — campos e sua ordem de declaração e métodos e sua ordem de declaração. E se mudarmos um tipo de campo e/ou o número de campos em nossa classe, o identificador de versão muda instantaneamente. O serialVersionUID também é escrito quando a classe é serializada. Quando tentamos desserializar, ou seja, restaurar um objeto de uma sequência de bytes, o valor de serialVersionUID é comparado com o valor de serialVersionUIDda classe em nosso programa. Se os valores não corresponderem, será lançada uma java.io.InvalidClassException. Veremos um exemplo disso a seguir. Para evitar tais situações, simplesmente definimos o identificador de versão para nossa classe manualmente. No nosso caso, será simplesmente igual a 1 (você pode usar qualquer outro número que desejar). Bem, é hora de tentar serializar nosso objeto SavedGame e ver o que acontece!

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       String[] territoryInfo = {"Spain has 6 provinces", "Russia has 10 provinces", "France has 8 provinces"};
       String[] resourceInfo = {"Spain has 100 gold", "Russia has 80 gold", "France has 90 gold"};
       String[] diplomacyInfo = {"France is at war with Russia, Spain has taken a neutral position"};

       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, diplomacyInfo);

       // Create 2 streams to serialize the object and save it to a file
       FileOutputStream outputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

       // Save the game to a file
       objectOutputStream.writeObject(savedGame);

       // Close the stream and release resources
       objectOutputStream.close();
   }
}
Como você pode ver, criamos 2 streams: FileOutputStream e ObjectOutputStream . O primeiro sabe como gravar dados no arquivo e o segundo converte objetos em bytes. Você já viu construções aninhadas semelhantes, por exemplo, new BufferedReader(new InputStreamReader(...)) , em lições anteriores, então elas não devem assustar você :) Ao criar esta cadeia de dois fluxos, realizamos as duas tarefas: convertemos o objeto SavedGame em uma sequência de bytes e o salvamos em um arquivo usando o método writeObject() . E, aliás, nem olhamos o que conseguimos! É hora de olhar para o arquivo! *Nota: não é necessário criar o arquivo com antecedência. Se um arquivo com o nome especificado não existir, ele será criado automaticamente* E aqui está seu conteúdo: ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ regionInfoq ~ xpur [Ljava.lang. String; СЏ заняла позицию нейтралит етаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотаt РЈ Р РѕСЃСЃРёРё 80 Р·РѕР»РѕС ‚Р°t !РЈ Франции 90 Р·РѕР »РѕС‚Р°uq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 dias atrás Oh, oh :( Parece que nosso programa não funcionou :( Na verdade, funcionou. Você lembra que enviamos uma sequência de bytes, e não simplesmente um objeto ou texto, para o arquivo? Bom, é isso que esta sequência de bytes parece :) É o nosso jogo salvo! Se quisermos restaurar nosso objeto original, ou seja, iniciar e continuar o jogo de onde paramos, então precisamos do processo inverso: desserialização. É assim que parece para nós:

import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);
   }
}
E aqui está o resultado! SavedGame{territoryInfo=[Espanha tem 6 províncias, Rússia tem 10 províncias, França tem 8 províncias], resourceInfo=[Espanha tem 100 ouro, Rússia tem 80 ouro, França tem 90 ouro], diplomacyInfo=[França está em guerra com a Rússia, A Espanha assumiu uma posição neutra]} Excelente! Conseguimos primeiro salvar o estado do nosso jogo em um arquivo e depois restaurá-lo a partir do arquivo. Agora vamos tentar fazer a mesma coisa, mas vamos remover o identificador de versão da nossa classe SavedGame . Não vamos reescrever nossas duas classes. Seu código será o mesmo. Apenas removeremos serialVersionUID longo final estático privado da classe SavedGame . Aqui está o nosso objeto após a serialização: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ regionInfoq ~ xpur [Ljava.lang.String;¬ТVзй{G xp t pФранция РІРѕСЋРµС ‚ СЃ Россией, Р˜СЃРїР°РЅРёСЏ заняла позицию нейтралитет Р°uq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотР°t РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &Р Ј Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 РїСЂРѕРІРё нций Mas veja o que acontece quando tentamos desserializá-lo: InvalidClassException: classe local incompatível: stream classdesc serialVersionUID = -196410440475012755, classe local serialVersionUID = -6675950253085108747 A propósito, perdemos algo importante. Obviamente, strings e primitivas são facilmente serializadas: Java certamente tem algum mecanismo interno para isso. Mas e se nossa classe serializável tiver campos que não sejam primitivos, mas referências a outros objetos? Por exemplo, vamos criar classes TerritoryInfo , ResourceInfo e DiplomacyInfo separadas para trabalhar com nossa classe SavedGame .

public class TerritoryInfo {

   private String info;

   public TerritoryInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "TerritoryInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class ResourceInfo {

   private String info;

   public ResourceInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "ResourceInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class DiplomacyInfo {

   private String info;

   public DiplomacyInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "DiplomacyInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}
E agora nos deparamos com uma pergunta: todas essas classes devem ser serializáveis ​​se quisermos serializar nossa classe SavedGame ?

import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private TerritoryInfo territoryInfo;
   private ResourceInfo resourceInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoryInfo territoryInfo, ResourceInfo resourceInfo, DiplomacyInfo diplomacyInfo) {
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public TerritoryInfo getTerritoryInfo() {
       return territoryInfo;
   }

   public void setTerritoryInfo(TerritoryInfo territoryInfo) {
       this.territoryInfo = territoryInfo;
   }

   public ResourceInfo getResourceInfo() {
       return resourceInfo;
   }

   public void setResourceInfo(ResourceInfo resourceInfo) {
       this.resourceInfo = resourceInfo;
   }

   public DiplomacyInfo getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(DiplomacyInfo diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoryInfo=" + territoryInfo +
               ", resourceInfo=" + resourceInfo +
               ", diplomacyInfo=" + diplomacyInfo +
               '}';
   }
}
Tudo bem, então! Vamos testar! Por enquanto, vamos deixar tudo como está e tentar serializar um objeto SavedGame :

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, diplomacyInfo);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}
Resultado: Exceção no thread "principal" java.io.NotSerializableException: DiplomacyInfo Não funcionou! Então, aqui está a resposta à nossa pergunta. Quando um objeto é serializado, todos os objetos referenciados por suas variáveis ​​de instância são serializados. E se esses objetos também fizerem referência a outros objetos, eles também serão serializados. E assim por diante para sempre. Todas as classes desta cadeia devem ser Serializable , caso contrário será impossível serializá-las e uma exceção será lançada. A propósito, isso pode criar problemas no futuro. Por exemplo, o que devemos fazer se não precisarmos de parte de uma classe durante a serialização? Ou, e se obtivermos nossa classe TerritoryInfo 'através de herança' como parte de uma biblioteca? E suponha ainda que não sejae, portanto, não podemos mudá-lo. Isso significaria que não podemos adicionar um campo TerritoryInfo à nossa classe SavedGame , porque toda a classe SavedGame se tornaria não serializável! Isso é um problema: / Serialização e desserialização em Java - 2Em Java, esse tipo de problema é resolvido pela palavra-chave transient . Se você adicionar essa palavra-chave a um campo de sua classe, esse campo não será serializado. Vamos tentar tornar um dos campos de nossa classe SavedGame transiente e, em seguida, serializaremos e restauraremos um único objeto.

import java.io.Serializable;

public class SavedGame implements Serializable {

   private transient TerritoryInfo territoryInfo;
   private ResourceInfo resourceInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoryInfo territoryInfo, ResourceInfo resourceInfo, DiplomacyInfo diplomacyInfo) {
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   // ...getters, setters, toString()...
}



import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, diplomacyInfo);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}


import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);

       objectInputStream.close();


   }
}
E aqui está o resultado: SavedGame{territoryInfo=null, resourceInfo=ResourceInfo{info='Espanha tem 100 de ouro, Rússia tem 80 de ouro, França tem 90 de ouro'}, diplomacyInfo=DiplomacyInfo{info='A França está em guerra com a Rússia, Espanha assumiu uma posição neutra'}} Dito isso, obtivemos uma resposta para a questão de qual valor será atribuído a um campo transitório . É atribuído o valor padrão. Para objetos, isso é null . Você pode ler um excelente capítulo sobre este tópico no livro 'Head-First Java', Preste atenção :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION