CodeGym /Java Blog /Random-IT /Interfaccia esternalizzabile in Java
John Squirrels
Livello 41
San Francisco

Interfaccia esternalizzabile in Java

Pubblicato nel gruppo Random-IT
CIAO! Oggi continueremo a conoscere la serializzazione e deserializzazione di oggetti Java . Nell'ultima lezione, abbiamo conosciuto l' interfaccia del marcatore Serializable , esaminato esempi del suo utilizzo e imparato anche come utilizzare la parola chiave transient per controllare il processo di serializzazione. Bene, dire che "controlliamo il processo" potrebbe essere un'esagerazione. Abbiamo una parola chiave, un identificatore di versione e questo è tutto. Il resto del processo è nascosto all'interno di Java e non possiamo accedervi. Naturalmente, in termini di praticità, questo va bene. Ma un programmatore non dovrebbe essere guidato solo dal proprio comfort, giusto? :) Ci sono altri fattori che devi considerare. Ecco perché serializzabilenon è l'unico meccanismo per la serializzazione-deserializzazione in Java. Oggi faremo conoscenza con l' interfaccia Externalizable . Ma prima di iniziare a studiarlo, potresti avere una domanda ragionevole: perché abbiamo bisogno di un altro meccanismo? Serializableha fatto il suo lavoro, e cosa c'è da non amare dell'implementazione automatica dell'intero processo? E anche gli esempi che abbiamo visto erano semplici. Allora, qual'è il problema? Perché abbiamo bisogno di un'altra interfaccia per essenzialmente le stesse attività? Il fatto è che Serializableha diversi difetti. Ne elenchiamo alcuni:
  1. Prestazione. L' Serializableinterfaccia ha molti vantaggi, ma chiaramente le alte prestazioni non sono tra queste.

    Introduzione all'interfaccia Externalizable - 2

    Innanzitutto, Serializable l'implementazione interna di genera una grande quantità di informazioni sul servizio e tutti i tipi di dati temporanei.

    In secondo luogo, Serializable si basa sull'API Reflection (non è necessario approfondire questo argomento in questo momento; puoi leggere di più a tuo piacimento, se sei interessato). Questa cosa ti consente di fare le cose apparentemente impossibili in Java: ad esempio, modificare i valori dei campi privati. CodeGym ha un eccellente articolo sull'API Reflection . Puoi leggerlo lì.

  2. Flessibilità. Non controlliamo il processo di serializzazione-deserializzazione quando utilizziamo l' Serializableinterfaccia.

    Da un lato, è molto conveniente, perché se non siamo particolarmente preoccupati per le prestazioni, allora sembra bello non dover scrivere codice. Ma cosa succede se abbiamo davvero bisogno di aggiungere alcune delle nostre funzionalità (forniremo un esempio di seguito) alla logica di serializzazione?

    Fondamentalmente, tutto ciò che abbiamo per controllare il processo è la transientparola chiave per escludere alcuni dati. Questo è tutto. Questa è la nostra intera cassetta degli attrezzi :/

  3. Sicurezza. Tale voce deriva in parte dalla voce precedente.

    Non abbiamo passato molto tempo a pensarci prima, ma cosa succede se alcune informazioni nella tua classe non sono destinate agli occhi e alle orecchie indiscreti degli altri? Un semplice esempio è una password o altri dati personali dell'utente, che nel mondo di oggi sono regolati da una serie di leggi.

    Se usiamo Serializable, non possiamo davvero farci niente. Serializziamo tutto così com'è.

    Ma se lo facciamo nel modo giusto, dobbiamo crittografare questo tipo di dati prima di scriverli su un file o inviarli su una rete. Ma Serializablenon lo rende possibile.

Introduzione all'interfaccia Externalizable - 3Bene, vediamo finalmente come sarebbe la classe se usiamo l' Externalizableinterfaccia.

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 {

   }
}
Come puoi vedere, abbiamo cambiamenti significativi! Il principale è ovvio: quando si implementa l' Externalizableinterfaccia, è necessario implementare due metodi richiesti: writeExternal()ereadExternal(). Come abbiamo detto in precedenza, la responsabilità della serializzazione e della deserializzazione ricadrà sul programmatore. Ma ora puoi risolvere il problema dell'assenza di controllo sul processo! L'intero processo è programmato direttamente da te. Naturalmente, questo consente un meccanismo molto più flessibile. Inoltre, il problema con la sicurezza è risolto. Come puoi vedere, la nostra classe ha un campo di dati personali che non può essere archiviato in chiaro. Ora possiamo facilmente scrivere codice che soddisfi questo vincolo. Ad esempio, possiamo aggiungere alla nostra classe due semplici metodi privati ​​per crittografare e decrittografare dati sensibili. Scriveremo i dati nel file e li leggeremo dal file in forma crittografata. Il resto dei dati verrà scritto e letto così com'è :) Di conseguenza, la nostra classe è 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 ObjectOutputparametri ObjectInputche abbiamo già incontrato nella lezione su Serializable. Al momento giusto, crittografiamo o decifriamo i dati richiesti e utilizziamo i dati crittografati per serializzare il nostro oggetto. Vediamo come appare in 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("Paul", "Piper", "Paul Piper's passport data");

       objectOutputStream.writeObject(userInfo);

       objectOutputStream.close();

   }
}
Nei metodi encryptString()e decryptString()abbiamo aggiunto in modo specifico l'output della console per verificare la forma in cui i dati segreti verranno scritti e letti. Il codice sopra mostrava la seguente riga: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh La crittografia è riuscita! Il contenuto completo del file è simile a questo: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Ora proviamo a usare la nostra logica di deserializzazione.

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, niente sembra complicato qui. Dovrebbe funzionare! Lo eseguiamo e otteniamo... Eccezione nel thread "main" java.io.InvalidClassException: UserInfo; nessun costruttore valido Presentazione dell'interfaccia Externalizable - 4 Spiacenti! :( Apparentemente, non è così facile! Il meccanismo di deserializzazione ha generato un'eccezione e ha richiesto la creazione di un costruttore predefinito. Mi chiedo perché. Con Serializable, ce la siamo cavata senza uno... :/ Qui abbiamo incontrato un'altra sfumatura importante. Il differenza tra Serializablee Externalizablerisiede non solo nell'accesso "espanso" del programmatore e nella capacità di controllare in modo più flessibile il processo, ma anche nel processo stesso e soprattutto nel meccanismo di deserializzazione .Serializable, la memoria viene semplicemente allocata per l'oggetto, quindi i valori vengono letti dal flusso e usati per impostare i campi dell'oggetto. Se usiamo Serializable, il costruttore dell'oggetto non viene chiamato! Tutto il lavoro avviene attraverso la riflessione (la Reflection API, che abbiamo brevemente menzionato nell'ultima lezione). Con Externalizable, il meccanismo di deserializzazione è diverso. Il costruttore predefinito viene chiamato per primo. Solo dopo viene chiamato il metodo UserInfodell'oggetto creato readExternal(). È responsabile dell'impostazione dei campi dell'oggetto. Ecco perché qualsiasi classe che implementa l' Externalizableinterfaccia deve avere un costruttore predefinito . Aggiungiamone uno alla nostra UserInfoclasse ed 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 Paul Piper UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'dati del passaporto di Paul Piper' } Ora è qualcosa di completamente diverso! Innanzitutto, la stringa decrittografata con informazioni segrete è stata visualizzata sulla console. Quindi l'oggetto che abbiamo recuperato dal file è stato visualizzato come una stringa! Quindi abbiamo risolto con successo tutti i problemi :) L'argomento della serializzazione e deserializzazione sembra semplice, ma, come puoi vedere, le lezioni sono state lunghe. E c'è molto di più che non abbiamo coperto! Ci sono ancora molte sottigliezze coinvolte nell'utilizzo di ciascuna di queste interfacce. Ma per evitare di farti esplodere il cervello da troppe nuove informazioni, elencherò brevemente alcuni punti più importanti e ti fornirò collegamenti a letture aggiuntive. Allora, cos'altro hai bisogno di sapere? Innanzitutto , durante la serializzazione (indipendentemente dal fatto che tu stia utilizzando Serializableo Externalizable), fai attenzione alle staticvariabili. Quando usi Serializable, questi campi non sono affatto serializzati (e, di conseguenza, i loro valori non cambiano, perché statici campi appartengono alla classe, non all'oggetto). Ma quando usiExternalizable, controlli tu stesso il processo, quindi tecnicamente potresti serializzarli. Ma non lo consigliamo, poiché è probabile che ciò crei molti bug sottili. In secondo luogo , dovresti anche prestare attenzione alle variabili con il finalmodificatore. Quando usi Serializable, vengono serializzati e deserializzati come al solito, ma quando usi Externalizable, è impossibile deserializzare una finalvariabile ! 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 con finalcampi, utilizzare la serializzazione standard fornita da Serializable. Terzo , quando usi l'ereditarietà, tutte le classi discendenti che ne ereditano alcuneExternalizableclass deve avere anche costruttori predefiniti. Ecco il collegamento a un buon articolo sui meccanismi di serializzazione: Fino alla prossima volta! :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION