CodeGym /Blogue Java /Random-PT /Interface externalizável em Java
John Squirrels
Nível 41
San Francisco

Interface externalizável em Java

Publicado no grupo Random-PT
Oi! Hoje continuaremos a conhecer a serialização e desserialização de objetos Java. Na última lição, conhecemos a interface do marcador Serializable , revisamos exemplos de seu uso e também aprendemos como você pode usar a palavra-chave transient para controlar o processo de serialização. Bem, dizer que 'controlamos o processo' pode ser um exagero. Temos uma palavra-chave, um identificador de versão e é isso. O restante do processo está oculto dentro do Java e não podemos acessá-lo. Claro, em termos de conveniência, isso é bom. Mas um programador não deve se guiar apenas pelo seu próprio conforto, certo? :) Existem outros fatores que você precisa considerar. É por isso que serializávelnão é o único mecanismo para serialização-desserialização em Java. Hoje vamos nos familiarizar com a interface Externalizável . Mas antes de começarmos a estudá-lo, você pode ter uma pergunta razoável: por que precisamos de outro mecanismo? Serializablefez o seu trabalho, e como não amar a implementação automática de todo o processo? E os exemplos que vimos também eram descomplicados. Então qual é o problema? Por que precisamos de outra interface para essencialmente as mesmas tarefas? O fato é que Serializabletem várias deficiências. Listamos alguns deles:
  1. Desempenho. A Serializableinterface tem muitas vantagens, mas o alto desempenho claramente não é uma delas.

    Apresentando a interface Externalizável - 2

    Primeiro, Serializable a implementação interna do gera uma grande quantidade de informações de serviço e todos os tipos de dados temporários.

    Em segundo lugar, Serializable depende da API de reflexão (você não precisa se aprofundar nisso agora; pode ler mais quando quiser, se estiver interessado). Isso permite que você faça coisas aparentemente impossíveis em Java: por exemplo, alterar os valores de campos privados. CodeGym tem um excelente artigo sobre a API Reflection . Você pode ler sobre isso lá.

  2. Flexibilidade. Não controlamos o processo de serialização-desserialização quando usamos a Serializableinterface.

    Por um lado, é muito conveniente, porque se não estamos particularmente preocupados com o desempenho, parece bom não ter que escrever código. Mas e se realmente precisarmos adicionar alguns de nossos próprios recursos (daremos um exemplo abaixo) à lógica de serialização?

    Basicamente, tudo o que temos para controlar o processo é a transientpalavra-chave para excluir alguns dados. É isso. Essa é toda a nossa caixa de ferramentas :/

  3. Segurança. Este item deriva em parte do item anterior.

    Não gastamos muito tempo pensando sobre isso antes, mas e se algumas informações em sua aula não forem destinadas aos olhares e ouvidos curiosos de outras pessoas? Um exemplo simples é uma senha ou outros dados pessoais do usuário, que no mundo de hoje são regidos por várias leis.

    Se usarmos Serializable, não podemos fazer nada a respeito. Serializamos tudo como está.

    Mas se fizermos isso da maneira certa, devemos criptografar esse tipo de dado antes de gravá-lo em um arquivo ou enviá-lo pela rede. Mas Serializablenão torna isso possível.

Apresentando a interface Externalizável - 3Bem, vamos finalmente ver como a classe ficaria se usássemos a Externalizableinterface.

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;

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

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

   }

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

   }
}
Como você pode ver, temos mudanças significativas! O principal é óbvio: ao implementar a Externalizableinterface, você deve implementar dois métodos obrigatórios: writeExternal()ereadExternal(). Como dissemos anteriormente, a responsabilidade pela serialização e desserialização será do programador. Mas agora você pode resolver o problema da falta de controle sobre o processo! Todo o processo é programado diretamente por você. Naturalmente, isso permite um mecanismo muito mais flexível. Além disso, o problema com a segurança é resolvido. Como você pode ver, nossa classe tem um campo de dados pessoais que não pode ser armazenado sem criptografia. Agora podemos facilmente escrever um código que satisfaça essa restrição. Por exemplo, podemos adicionar à nossa classe dois métodos privados simples para criptografar e descriptografar dados confidenciais. Vamos gravar os dados no arquivo e lê-los no arquivo de forma criptografada. O resto dos dados serão escritos e lidos como estão :) Como resultado, nossa classe se parece com isto:

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 utilizam os mesmos parâmetros ObjectOutpute ObjectInputque já conhecemos na aula sobre Serializable. No momento certo, criptografamos ou descriptografamos os dados necessários e usamos os dados criptografados para serializar nosso objeto. Vamos ver como isso fica 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("Paul", "Piper", "Paul Piper's passport data");

       objectOutputStream.writeObject(userInfo);

       objectOutputStream.close();

   }
}
Nos métodos encryptString()e decryptString(), adicionamos especificamente a saída do console para verificar a forma na qual os dados secretos serão gravados e lidos. O código acima exibia a seguinte linha: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh A criptografia foi bem-sucedida! O conteúdo completo do arquivo é assim: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Agora vamos tentar usar nossa lógica de desserialização.

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, nada parece complicado aqui. Deve funcionar! Nós o executamos e obtemos... Exceção no thread "main" java.io.InvalidClassException: UserInfo; nenhum construtor válido Apresentando a interface Externalizável - 4 Ops! :( Aparentemente, não é tão fácil! O mecanismo de desserialização lançou uma exceção e exigiu que criássemos um construtor padrão. Eu me pergunto por que. Com Serializable, passamos sem um... :/ Aqui encontramos outra nuance importante. O A diferença entre Serializablee Externalizablereside não apenas no acesso 'expandido' do programador e na capacidade de controlar o processo com mais flexibilidade, mas também no próprio processo. Acima de tudo, no mecanismo de desserialização . Ao usarSerializable, a memória é simplesmente alocada para o objeto e, em seguida, os valores são lidos do fluxo e usados ​​para definir os campos do objeto. Se usarmos Serializable, o construtor do objeto não é chamado! Todo o trabalho acontece por meio de reflexão (a API de reflexão, que mencionamos brevemente na última lição). Com Externalizable, o mecanismo de desserialização é diferente. O construtor padrão é chamado primeiro. Somente depois disso é chamado o método UserInfodo objeto criado. readExternal()É responsável por configurar os campos do objeto. É por isso que qualquer classe que implementa a Externalizableinterface deve ter um construtor padrão . Vamos adicionar um à nossa UserInfoclasse e 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 Paul Piper UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Dados do passaporte de Paul Piper' } Isso é algo completamente diferente! Primeiro, a string descriptografada com informações secretas foi exibida no console. Em seguida, o objeto que recuperamos do arquivo foi exibido como uma string! Portanto, resolvemos todos os problemas com sucesso :) O tópico de serialização e desserialização parece simples, mas, como você pode ver, as lições foram longas. E há muito mais que não cobrimos! Ainda existem muitas sutilezas envolvidas ao usar cada uma dessas interfaces. Mas, para evitar explodir seu cérebro com o excesso de novas informações, listarei brevemente mais alguns pontos importantes e fornecerei links para leitura adicional. Então, o que mais você precisa saber? Primeiro , durante a serialização (independentemente de você estar usando Serializableou Externalizable), preste atenção às staticvariáveis. Quando você usa Serializable, esses campos não são serializados (e, portanto, seus valores não mudam, porque staticos campos pertencem à classe, não ao objeto). Mas quando você usaExternalizable, você mesmo controla o processo, portanto, tecnicamente, você pode serializá-los. Mas não o recomendamos, pois isso provavelmente criará muitos bugs sutis. Em segundo lugar , você também deve prestar atenção às variáveis ​​com o finalmodificador. Quando você usa Serializable, eles são serializados e desserializados como de costume, mas quando você usa Externalizable, é impossível desserializar uma finalvariável ! O motivo é simples: todos finalos campos são inicializados quando o construtor padrão é chamado — depois disso, seu valor não pode ser alterado. Portanto, para serializar objetos que possuem finalcampos, utilize a serialização padrão fornecida pelo Serializable. Terceiro , quando você usa herança, todas as classes descendentes que herdam algumExternalizableclasse também deve ter construtores padrão. Aqui está o link para um bom artigo sobre mecanismos de serialização: Até a próxima vez! :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION