CodeGym /Blog Java /Aleatoriu /Interfață externabilă în Java
John Squirrels
Nivel
San Francisco

Interfață externabilă în Java

Publicat în grup
Bună! Astăzi vom continua să cunoaștem serializarea și deserializarea obiectelor Java . În ultima lecție, am făcut cunoștință cu interfața markerului Serializable , am analizat exemple de utilizare a acesteia și, de asemenea, am învățat cum puteți utiliza cuvântul cheie tranzitoriu pentru a controla procesul de serializare. Ei bine, a spune că „controlăm procesul” ar putea fi exagerat. Avem un cuvânt cheie, un identificator de versiune și cam atât. Restul procesului este ascuns în Java și nu îl putem accesa. Desigur, din punct de vedere al confortului, acest lucru este bun. Dar un programator nu ar trebui să fie ghidat doar de propriul confort, nu? :) Există și alți factori pe care trebuie să îi luați în considerare. De aceea serializabilnu este singurul mecanism de serializare-deserializare în Java. Astăzi ne vom familiariza cu interfața Externalizabil . Dar înainte de a începe să-l studiem, s-ar putea să aveți o întrebare rezonabilă: de ce avem nevoie de un alt mecanism? Serializableși-a făcut treaba și ce nu e de iubit la implementarea automată a întregului proces? Și exemplele pe care le-am uitat au fost, de asemenea, necomplicate. Deci care este problema? De ce avem nevoie de o altă interfață pentru, în esență, aceleași sarcini? Cert este că Serializableare mai multe deficiențe. Enumerăm câteva dintre ele:
  1. Performanţă. Interfața Serializableare multe avantaje, dar performanța ridicată nu este în mod clar unul dintre ele.

    Prezentarea interfeței Externalizabile - 2

    În primul rând, Serializable implementarea internă a lui generează o cantitate mare de informații despre servicii și tot felul de date temporare.

    În al doilea rând, Serializable se bazează pe API-ul Reflection (nu trebuie să vă aprofundați în acest moment; puteți citi mai multe pe îndelete, dacă sunteți interesat). Acest lucru vă permite să faceți lucrurile aparent imposibile în Java: de exemplu, să schimbați valorile câmpurilor private. CodeGym are un articol excelent despre API-ul Reflection . Puteți citi despre asta acolo.

  2. Flexibilitate. Nu controlăm procesul de serializare-deserializare atunci când folosim interfața Serializable.

    Pe de o parte, este foarte convenabil, pentru că dacă nu ne preocupă în mod deosebit performanța, atunci pare bine să nu trebuie să scriem cod. Dar ce se întâmplă dacă într-adevăr trebuie să adăugăm unele dintre propriile noastre caracteristici (vom oferi un exemplu mai jos) la logica de serializare?

    Practic, tot ce avem pentru a controla procesul este transientcuvântul cheie pentru a exclude unele date. Asta este. Aceasta este întreaga noastră cutie de instrumente :/

  3. Securitate. Acest articol derivă parțial din articolul anterior.

    Nu am petrecut mult timp gândindu-ne la asta până acum, dar ce se întâmplă dacă unele informații din clasa ta nu sunt destinate privirilor și urechilor celorlalți? Un exemplu simplu este o parolă sau alte date personale ale utilizatorului, care în lumea de astăzi sunt guvernate de o grămadă de legi.

    Dacă folosim Serializable, nu putem face nimic în privința asta. Serializăm totul așa cum este.

    Dar dacă o facem corect, trebuie să criptăm acest tip de date înainte de a le scrie într-un fișier sau de a le trimite printr-o rețea. Dar Serializablenu face acest lucru posibil.

Prezentarea interfeței Externalizabile - 3Ei bine, să vedem în cele din urmă cum ar arăta clasa dacă folosim interfața 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;

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

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

   }

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

   }
}
După cum puteți vedea, avem schimbări semnificative! Principala este evidentă: atunci când implementați Externalizableinterfața, trebuie să implementați două metode necesare: writeExternal()șireadExternal(). După cum am spus mai devreme, responsabilitatea pentru serializare și deserializare va reveni programatorului. Dar acum puteți rezolva problema lipsei de control asupra procesului! Întregul proces este programat direct de dvs. Desigur, acest lucru permite un mecanism mult mai flexibil. În plus, problema cu securitatea este rezolvată. După cum puteți vedea, clasa noastră are un câmp de date personale care nu poate fi stocat necriptat. Acum putem scrie cu ușurință cod care satisface această constrângere. De exemplu, putem adăuga la clasa noastră două metode private simple pentru a cripta și decripta datele sensibile. Vom scrie datele în fișier și le vom citi din fișier în formă criptată. Restul datelor vor fi scrise și citite așa cum sunt :) Prin urmare, clasa noastră arată cam așa:

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;
   }
}
Am implementat două metode care folosesc același lucru ObjectOutputși ObjectInputparametri pe care i-am întâlnit deja în lecția despre Serializable. La momentul potrivit, criptăm sau decriptăm datele necesare și folosim datele criptate pentru a serializa obiectul nostru. Să vedem cum arată asta în practică:

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();

   }
}
În metodele encryptString()și decryptString(), am adăugat în mod specific ieșirea consolei pentru a verifica forma în care vor fi scrise și citite datele secrete. Codul de mai sus afișa următoarea linie: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Criptarea a reușit! Conținutul complet al fișierului arată astfel: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivanovt Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Acum, să încercăm să folosim logica noastră de deserializare.

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();

   }
}
Ei bine, nimic nu pare complicat aici. Ar trebui să funcționeze! Îl rulăm și obținem... Excepție în firul „principal” java.io.InvalidClassException: UserInfo; nici un constructor valid Prezentarea interfeței Externalizabile - 4 Hopa! :( Aparent, nu este atât de ușor! Mecanismul de deserializare a făcut o excepție și a cerut să creăm un constructor implicit. Mă întreb de ce. Cu , ne-am Serializabledescurcat fără unul... :/ Aici am întâlnit o altă nuanță importantă. diferența dintre Serializableși Externalizablenu constă numai în accesul „extins” al programatorului și în capacitatea de a controla mai flexibil procesul, ci și în procesul în sine. Mai presus de toate, în mecanismul de deserializare . Când utilizațiSerializable, memoria este pur și simplu alocată pentru obiect, iar apoi valorile sunt citite din flux și utilizate pentru a seta câmpurile obiectului. Dacă folosim Serializable, constructorul obiectului nu este numit! Toată munca se întâmplă prin reflecție (API-ul Reflection, pe care l-am menționat pe scurt în ultima lecție). Cu Externalizable, mecanismul de deserializare este diferit. Constructorul implicit este numit primul. Abia după aceea este numită metoda UserInfoobiectului creat readExternal(). Este responsabil pentru setarea câmpurilor obiectului. De aceea, orice clasă care implementează Externalizableinterfața trebuie să aibă un constructor implicit . Să adăugăm unul la UserInfoclasa noastră și să rulăm din nou codul:

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();
   }
}
Ieșire din consolă: datele pașaportului lui Paul Piper UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Datele pașaportului lui Paul Piper' } Acum asta e cu totul altceva! Mai întâi, șirul decriptat cu informații secrete a fost afișat pe consolă. Apoi obiectul pe care l-am recuperat din fișier a fost afișat ca șir! Deci am rezolvat cu succes toate problemele :) Tema serializării și deserializării pare simplă, dar, după cum puteți vedea, lecțiile au fost lungi. Și sunt multe altele pe care nu le-am acoperit! Există încă multe subtilități implicate atunci când utilizați fiecare dintre aceste interfețe. Dar pentru a evita să vă explodeze creierul de la informații noi excesive, voi enumera pe scurt câteva puncte mai importante și vă voi oferi link-uri către lecturi suplimentare. Deci, ce altceva trebuie să știți? În primul rând , în timpul serializării (indiferent dacă utilizați Serializablesau Externalizable), acordați atenție staticvariabilelor. Când utilizați Serializable, aceste câmpuri nu sunt deloc serializate (și, în consecință, valorile lor nu se modifică, deoarece staticcâmpurile aparțin clasei, nu obiectului). Dar când foloseștiExternalizable, controlați singur procesul, așa că din punct de vedere tehnic le puteți serializa. Dar, nu îl recomandăm, deoarece acest lucru poate crea o mulțime de erori subtile. În al doilea rând , ar trebui să acordați atenție și variabilelor cu finalmodificatorul. Când utilizați Serializable, acestea sunt serializate și deserializate ca de obicei, dar când utilizați Externalizable, este imposibil să deserializați o finalvariabilă ! Motivul este simplu: toate finalcâmpurile sunt inițializate atunci când este apelat constructorul implicit - după aceea, valoarea lor nu poate fi modificată. Prin urmare, pentru a serializa obiecte care au finalcâmpuri, utilizați serializarea standard oferită de Serializable. În al treilea rând , atunci când utilizați moștenirea, toate clasele descendente care moștenesc uneleExternalizableclasa trebuie să aibă și constructori impliciti. Iată un link către un articol bun despre mecanismele de serializare: Pana data viitoare! :)
Comentarii
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION