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ă Serializable
are mai multe deficiențe. Enumerăm câteva dintre ele:
-
Performanţă. Interfața
Serializable
are multe avantaje, dar performanța ridicată nu este în mod clar unul dintre ele.Î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. -
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
transient
cuvântul cheie pentru a exclude unele date. Asta este. Aceasta este întreaga noastră cutie de instrumente :/ -
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
Serializable
nu face acest lucru posibil.

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 Externalizable
interfaț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 ObjectInput
parametri 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 
Serializable
descurcat fără unul... :/ Aici am întâlnit o altă nuanță importantă. diferența dintre Serializable
și Externalizable
nu 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 UserInfo
obiectului creat readExternal()
. Este responsabil pentru setarea câmpurilor obiectului. De aceea, orice clasă care implementează Externalizable
interfața trebuie să aibă un constructor implicit . Să adăugăm unul la UserInfo
clasa 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 Serializable
sau Externalizable
), acordați atenție static
variabilelor. Când utilizați Serializable
, aceste câmpuri nu sunt deloc serializate (și, în consecință, valorile lor nu se modifică, deoarece static
câ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 final
modificatorul. 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 final
variabilă ! Motivul este simplu: toate final
câ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 final
câ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 uneleExternalizable
clasa trebuie să aibă și constructori impliciti. Iată un link către un articol bun despre mecanismele de serializare:
Pana data viitoare! :)
GO TO FULL VERSION