JavaRush /Blogue Java /Random-PT /Interface externalizável em Java

Interface externalizável em Java

Publicado no grupo Random-PT
Olá! Hoje continuaremos nossa introdução à serialização e desserialização de objetos Java. Na última palestra, fomos apresentados à interface do marcador Serializable , vimos exemplos de seu uso e também aprendemos como controlar o processo de serialização usando a palavra-chave transitória . Bem, “gerenciar o processo”, é claro, é uma palavra forte. Temos uma palavra-chave, um ID de versão e é basicamente isso. O resto do processo é “conectado” dentro do Java e não há acesso a ele. Do ponto de vista da conveniência, isso é, obviamente, bom. Mas um programador em seu trabalho não deve focar apenas no seu próprio conforto, certo? :) Existem outros fatores a serem considerados. Portanto, Serializable não é a única ferramenta para serialização-desserialização em Java. Hoje vamos nos familiarizar com a interface externalizável . Mas mesmo antes de começarmos a estudá-lo, você pode ter uma pergunta razoável: por que precisamos de outra ferramenta? SerializableCumpri meu trabalho e a implementação automática de todo o processo não pode deixar de me alegrar. Os exemplos que vimos também não foram complicados. Então qual é o problema? Por que outra interface para essencialmente a mesma tarefa? O fato é que Serializabletem uma série de desvantagens. Vamos listar alguns deles:
  1. Desempenho. A interface tem Serializablemuitas vantagens, mas o alto desempenho claramente não é uma delas.

Apresentando a interface externalizável - 2

Em primeiro lugar , o mecanismo interno Serializablegera uma grande quantidade de informações de serviço e vários tipos de dados temporários durante a operação.
Em segundo lugar (você não precisa entrar nisso agora e ler quando quiser, se estiver interessado), o trabalho Serializableé baseado no uso da API Reflection. Essa engenhoca permite fazer coisas que pareceriam impossíveis em Java: por exemplo, alterar os valores de campos privados. JavaRush tem um excelente artigo sobre a API Reflection , você pode ler sobre ele aqui.

  1. Flexibilidade. Não controlamos o processo de serialização-desserialização ao usar o Serializable.

    Por um lado, isso é muito conveniente, porque se não nos importamos realmente com o desempenho, a capacidade de não escrever código parece conveniente. Mas e se realmente precisarmos adicionar alguns de nossos próprios recursos (um exemplo de um deles estará abaixo) à lógica de serialização?

    Essencialmente, tudo o que temos para controlar o processo é uma palavra-chave transientpara excluir alguns dados, e pronto. Mais ou menos como um “kit de ferramentas” :/

  2. Segurança. Este ponto decorre parcialmente do anterior.

    Não pensamos muito sobre isso antes, mas e se alguma informação da sua aula não for destinada aos “ouvidos de outras pessoas” (mais precisamente, aos olhos)? Um exemplo simples é uma senha ou outros dados pessoais do usuário, que no mundo moderno são regulamentados por uma série de leis.

    Usando Serializable, na verdade não podemos fazer nada a respeito. Serializamos tudo como está.

    Mas, no bom sentido, devemos criptografar esse tipo de dados antes de gravá-los em um arquivo ou transmiti-los pela rede. Mas Serializablenão dá essa oportunidade.

Apresentando a Interface Externalizável - 3Bem, vamos finalmente ver como seria uma classe usando o Externalizable.
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class UserInfo implements Externalizable {

   private String firstName;
   private String lastName;
   private String superSecretInformation;

private static final long SERIAL_VERSION_UID = 1L;

   //...конструктор, геттеры, сеттеры, toString()...

   @Override
   public void writeExternal(ObjectOutput out) throws IOException {

   }

   @Override
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

   }
}
Como você pode ver, fizemos mudanças significativas! A principal delas é óbvia: ao implementar uma interface, Externalizablevocê deve implementar dois métodos obrigatórios - writeExternal()e readExternal(). Como dissemos anteriormente, toda a responsabilidade pela serialização e desserialização será do programador. Porém, agora você pode resolver o problema da falta de controle desse processo! Todo o processo é programado diretamente por você, o que, claro, cria um mecanismo muito mais flexível. Além disso, o problema de segurança também está resolvido. Como você pode ver, temos um campo em nossa classe: dados pessoais que não podem ser armazenados sem criptografia. Agora podemos escrever facilmente um código que atenda a essa restrição. Por exemplo, adicione dois métodos privados simples à nossa classe para criptografar e descriptografar dados secretos. Iremos gravá-los em um arquivo e lê-los no arquivo de forma criptografada. E vamos escrever e ler o restante dos dados como estão :) Como resultado, nossa classe ficará mais ou menos assim:
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Base64;

public class UserInfo implements Externalizable {

   private String firstName;
   private String lastName;
   private String superSecretInformation;

   private static final long serialVersionUID = 1L;

   public UserInfo() {
   }

   public UserInfo(String firstName, String lastName, String superSecretInformation) {
       this.firstName = firstName;
       this.lastName = lastName;
       this.superSecretInformation = superSecretInformation;
   }

   @Override
   public void writeExternal(ObjectOutput out) throws IOException {
       out.writeObject(this.getFirstName());
       out.writeObject(this.getLastName());
       out.writeObject(this.encryptString(this.getSuperSecretInformation()));
   }

   @Override
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
       firstName = (String) in.readObject();
       lastName = (String) in.readObject();
       superSecretInformation = this.decryptString((String) in.readObject());
   }

   private String encryptString(String data) {
       String encryptedData = Base64.getEncoder().encodeToString(data.getBytes());
       System.out.println(encryptedData);
       return encryptedData;
   }

   private String decryptString(String data) {
       String decrypted = new String(Base64.getDecoder().decode(data));
       System.out.println(decrypted);
       return decrypted;
   }

   public String getFirstName() {
       return firstName;
   }

   public String getLastName() {
       return lastName;
   }

   public String getSuperSecretInformation() {
       return superSecretInformation;
   }
}
Implementamos dois métodos que usam os mesmos ObjectOutput outparâmetros ObjectInputque já encontramos na palestra sobre Serializable. No momento certo, criptografamos ou descriptografamos os dados necessários e, desta forma, os utilizamos para serializar nosso objeto. Vamos ver como isso ficará na prática:
import java.io.*;

public class Main {

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

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

       UserInfo userInfo = new UserInfo("Ivan", "Ivanov", "Ivan Ivanov's passport data");

       objectOutputStream.writeObject(userInfo);

       objectOutputStream.close();

   }
}
Nos métodos encryptString()e decryptString(), adicionamos especificamente uma saída ao console para verificar de que forma os dados secretos serão gravados e lidos. O código acima gera a seguinte linha no console: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Criptografia bem-sucedida! O conteúdo completo do arquivo é assim: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Agora vamos tentar usar a lógica de desserialização que escrevemos.
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);


       UserInfo userInfo = (UserInfo) objectInputStream.readObject();
       System.out.println(userInfo);

       objectInputStream.close();

   }
}
Bem, não parece haver nada complicado aqui, deve funcionar! Vamos executar... Exceção no thread "main" java.io.InvalidClassException: UserInfo; nenhum construtor válido Apresentando a interface externalizável - 4 Ops :( Acontece que não é tão simples! O mecanismo de desserialização lançou uma exceção e exigiu que criássemos um construtor padrão. Eu me pergunto por quê? SerializableConseguimos sem ele... :/ Aqui chegamos a outra nuance importante A diferença entre Serializablee Externalizablereside não apenas no acesso “estendido” do programador e na capacidade de gerenciar o processo com mais flexibilidade, mas também no próprio processo. Em primeiro lugar, no mecanismo de desserialização ... Quando usada, Serializablea memória é simplesmente alocado para um objeto, após o qual os valores são lidos do fluxo, que preenchem todos os seus campos . Se usarmos Serializable, o construtor do objeto não é chamado! Todo o trabalho é feito por meio de reflexão (API Reflection, que mencionamos brevemente no último palestra). No caso de , o Externalizablemecanismo de desserialização será diferente. No início é chamado o construtor padrão. E só depois UserInfoé chamado no objeto criado o método readExternal(), que é responsável por preencher os campos do objeto. Ou seja por que qualquer classe que implementa a interface Externalizabledeve ter um construtor padrão . Vamos adicioná-lo à nossa classe UserInfoe executar novamente o código:
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);


       UserInfo userInfo = (UserInfo) objectInputStream.readObject();
       System.out.println(userInfo);

       objectInputStream.close();
   }
}
Saída do console: dados do passaporte de Ivan Ivanov UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='dados do passaporte de Ivan Ivanov'} Um assunto completamente diferente! Primeiro, a string descriptografada com dados secretos foi enviada para o console e, em seguida, nosso objeto foi recuperado do arquivo em formato de string! Foi assim que resolvemos todos os problemas com sucesso :) O tema serialização e desserialização parece simples, mas como você pode ver, nossas palestras acabaram sendo longas. E isso não é tudo! Existem muito mais sutilezas ao usar cada uma dessas interfaces, mas para que agora seu cérebro não exploda com o volume de novas informações, listarei brevemente mais alguns pontos importantes e fornecerei links para leituras adicionais. Então, o que mais você precisa saber? Primeiramente , ao serializar (não importa se você usa Serializableou Externalizable), preste atenção nas variáveis static. Quando usados, Serializableesses campos não são serializados (e, portanto, seu valor não muda, pois staticos campos pertencem à classe e não ao objeto). Mas ao usá-lo, Externalizablevocê mesmo controla o processo, então tecnicamente isso pode ser feito. Mas não é recomendado, pois está repleto de erros sutis. Em segundo lugar , deve-se prestar atenção também às variáveis ​​com o modificador final. Quando usados, Serializableeles são serializados e desserializados normalmente, mas quando usados, é impossível Externalizabledesserializar finaluma variável ! A razão é simples: todos finalos campos são inicializados quando o construtor padrão é chamado e, depois disso, seu valor não pode ser alterado. Portanto, para serializar objetos contendo final-fields, use a serialização padrão via Serializable. Terceiro , ao usar herança, todas as classes herdadas descendentes de alguma Externalizableclasse também devem ter construtores padrão. Aqui estão alguns links para bons artigos sobre mecanismos de serialização: Vê você! :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION