JavaRush /Java Blog /Random-IT /Interfaccia esternalizzabile in Java

Interfaccia esternalizzabile in Java

Pubblicato nel gruppo Random-IT
Ciao! Oggi continueremo la nostra introduzione alla serializzazione e alla deserializzazione degli oggetti Java. Nell'ultima lezione, ci è stata presentata l' interfaccia del marcatore Serializable , abbiamo esaminato esempi del suo utilizzo e abbiamo anche imparato come controllare il processo di serializzazione utilizzando la parola chiave transitoria . Ebbene, “gestire il processo”, ovviamente, è una parola forte. Abbiamo una parola chiave, un ID versione e fondamentalmente è tutto. Il resto del processo è “cablato” all'interno di Java e non è possibile accedervi. Dal punto di vista della comodità, questo è, ovviamente, positivo. Ma un programmatore nel suo lavoro non dovrebbe concentrarsi solo sulla propria comodità, giusto? :) Ci sono altri fattori da considerare. Pertanto, Serializable non è l'unico strumento per la serializzazione-deserializzazione in Java. Oggi faremo conoscenza con l' interfaccia Externalizable . Ma anche prima di passare allo studio, potresti avere una domanda ragionevole: perché abbiamo bisogno di un altro strumento? SerializableHo affrontato il mio lavoro e l'implementazione automatica dell'intero processo non può che rallegrarmi. Anche gli esempi che abbiamo esaminato non erano complicati. Allora, qual è il problema? Perché un'altra interfaccia essenzialmente per la stessa attività? Il fatto è che Serializablepresenta una serie di svantaggi. Ne elenchiamo alcuni:
  1. Prestazione. L'interfaccia presenta Serializablemolti vantaggi, ma le prestazioni elevate chiaramente non sono uno di questi.

Presentazione dell'interfaccia esternalizzabile - 2

Innanzitutto , il meccanismo interno Serializablegenera una grande quantità di informazioni di servizio e vari tipi di dati temporanei durante il funzionamento.
In secondo luogo (non è necessario approfondire questo argomento adesso e leggere a tuo piacimento se sei interessato), il lavoro Serializablesi basa sull'utilizzo dell'API Reflection. Questo aggeggio permette di fare cose che sembrerebbero impossibili in Java: ad esempio, modificare i valori dei campi privati. JavaRush ha pubblicato un eccellente articolo sull'API Reflection , puoi leggerlo qui.

  1. Flessibilità. Non controlliamo affatto il processo di serializzazione-deserializzazione quando si utilizza Serializable.

    Da un lato, questo è molto conveniente, perché se non ci interessano veramente le prestazioni, la possibilità di non scrivere codice sembra conveniente. Ma cosa succede se abbiamo davvero bisogno di aggiungere alcune delle nostre funzionalità (un esempio di una di esse sarà riportato di seguito) alla logica di serializzazione?

    In sostanza, tutto ciò che dobbiamo controllare il processo è una parola chiave transientper escludere alcuni dati, e il gioco è fatto. Una specie di "kit di strumenti" :/

  2. Sicurezza. Questo punto segue in parte dal precedente.

    Non ci abbiamo pensato molto prima, ma cosa succederebbe se alcune informazioni nella tua classe non fossero destinate alle “orecchie degli altri” (più precisamente, agli occhi)? Un semplice esempio è una password o altri dati personali dell'utente, che nel mondo moderno sono regolati da una serie di leggi.

    Utilizzando Serializable, in realtà non possiamo farci nulla. Serializziamo tutto così com'è.

    Ma, in senso buono, dobbiamo crittografare questo tipo di dati prima di scriverli su un file o trasmetterli in rete. Ma Serializablenon offre questa opportunità.

Presentazione dell'interfaccia esternalizzabile - 3Bene, vediamo finalmente come apparirebbe una classe utilizzando l'estensione 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 {

   }
}
Come puoi vedere, abbiamo apportato modifiche significative! Quello principale è ovvio: quando si implementa un'interfaccia, Externalizableè necessario implementare due metodi obbligatori: writeExternal()e readExternal(). Come abbiamo detto prima, tutta la responsabilità della serializzazione e deserializzazione ricadrà sul programmatore. Tuttavia, ora puoi risolvere il problema della mancanza di controllo su questo processo! L'intero processo è programmato direttamente da te, il che, ovviamente, crea un meccanismo molto più flessibile. Inoltre, anche il problema della sicurezza è risolto. Come puoi vedere, nella nostra classe abbiamo un campo: dati personali che non possono essere archiviati in chiaro. Ora possiamo scrivere facilmente codice che soddisfi questo vincolo. Ad esempio, aggiungi due semplici metodi privati ​​alla nostra classe per crittografare e decrittografare i dati segreti. Li scriveremo in un file e li leggeremo dal file in forma crittografata. E scriveremo e leggeremo il resto dei dati così come sono :) Di conseguenza, la nostra classe sarà simile a questa:
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;
   }
}
Abbiamo implementato due metodi che utilizzano gli stessi parametri ObjectOutput oute ObjectInputche abbiamo già incontrato nella lezione su Serializable. Al momento giusto, crittifichiamo o decodifichiamo i dati necessari e in questa forma li utilizziamo per serializzare il nostro oggetto. Vediamo come apparirà nella pratica:
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();

   }
}
Nei metodi encryptString()e decryptString()abbiamo aggiunto specificatamente l'output alla console per verificare in quale forma verranno scritti e letti i dati segreti. Il codice sopra restituisce la seguente riga alla console: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Crittografia riuscita! Il contenuto completo del file è simile al seguente: ¬н sr UserInfoГ!}īџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Ora proviamo a utilizzare la logica di deserializzazione che abbiamo scritto.
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();

   }
}
Bene, non sembra esserci nulla di complicato qui, dovrebbe funzionare! Eseguiamo... Eccezione nel thread "main" java.io.InvalidClassException: UserInfo; nessun costruttore valido Presentazione dell'interfaccia esternalizzabile - 4 Ops :( Si è rivelato non così semplice! Il meccanismo di deserializzazione ha generato un'eccezione e ci ha richiesto di creare un costruttore predefinito. Mi chiedo perché? SerializableSiamo riusciti a farne a meno... :/ Qui arriviamo a un'altra sfumatura importante La differenza tra Serializablee Externalizablenon sta solo nell'accesso "esteso" per il programmatore e nella capacità di gestire in modo più flessibile il processo, ma anche nel processo stesso. Innanzitutto, nel meccanismo di deserializzazione ... Quando viene utilizzata, Serializablela memoria viene semplicemente allocato per un oggetto, dopodiché vengono letti i valori dallo stream, che riempiono tutti i suoi campi. Se usiamo Serializable, il costruttore dell'oggetto non viene chiamato! Tutto il lavoro viene svolto tramite la riflessione (Reflection API, di cui abbiamo brevemente parlato nell'ultimo lezione). Nel caso di , il Externalizablemeccanismo di deserializzazione sarà diverso. All'inizio viene chiamato il costruttore predefinito. E solo dopo UserInfoviene chiamato il metodo dell'oggetto creato readExternal(), che è responsabile della compilazione dei campi dell'oggetto. Cioè perché ogni classe che implementa l'interfaccia Externalizabledeve avere un costruttore predefinito . Aggiungiamolo alla nostra classe UserInfoed eseguiamo nuovamente il codice:
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();
   }
}
Output della console: dati del passaporto di Ivan Ivanov UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='dati del passaporto di Ivan Ivanov'} Una questione completamente diversa! Innanzitutto, la stringa decrittografata con i dati segreti è stata inviata alla console, quindi il nostro oggetto è stato recuperato dal file in formato stringa! È così che abbiamo risolto con successo tutti i problemi :) L'argomento della serializzazione e deserializzazione sembra semplice, ma come puoi vedere, le nostre lezioni si sono rivelate lunghe. E non è tutto! Ci sono molte più sottigliezze quando si utilizza ciascuna di queste interfacce, ma affinché ora il tuo cervello non esploda dal volume di nuove informazioni, elencherò brevemente alcuni punti più importanti e fornirò collegamenti a letture aggiuntive. Quindi cos'altro hai bisogno di sapere? Innanzitutto , durante la serializzazione (non importa se usi Serializableo Externalizable), presta attenzione alle variabili static. Quando vengono utilizzati, Serializablequesti campi non vengono affatto serializzati (e, di conseguenza, il loro valore non cambia, poiché statici campi appartengono alla classe, non all'oggetto). Ma quando lo usi, Externalizablecontrolli tu stesso il processo, quindi tecnicamente questo può essere fatto. Ma non è consigliabile, poiché è pieno di errori sottili. In secondo luogo , occorre prestare attenzione anche alle variabili con il modificatore final. Quando vengono utilizzati, Serializablevengono serializzati e deserializzati come al solito, ma quando vengono utilizzati è impossibile Externalizabledeserializzare finaluna variabile ! Il motivo è semplice: tutti finali campi vengono inizializzati quando viene chiamato il costruttore predefinito, dopodiché il loro valore non può essere modificato. Pertanto, per serializzare oggetti contenenti final-fields, utilizzare la serializzazione standard tramite Serializable. In terzo luogo , quando si utilizza l'ereditarietà, anche tutte le classi discendenti che discendono da una Externalizableclasse devono avere costruttori predefiniti. Ecco alcuni collegamenti ad articoli interessanti sui meccanismi di serializzazione: Ci vediamo! :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION