Szia! Ma folytatjuk a Java objektumok szerializálásának és deszerializálásának megismerését . Az utolsó leckében megismertük a Serializable marker felületet, áttekintettük a felhasználási példákat, és azt is megtanultuk, hogyan lehet a tranziens kulcsszóval irányítani a szerializálási folyamatot. Nos, ha azt mondjuk, hogy mi irányítjuk a folyamatot, az talán túlzásba esik. Van egy kulcsszónk, egy verzióazonosítónk, és nagyjából ennyi. A folyamat többi része a Java belsejében van elrejtve, és nem férünk hozzá. Persze kényelmi szempontból ez jó. De egy programozót nem csak a saját kényelme vezérelhet, igaz? :) Vannak más tényezők is, amelyeket figyelembe kell venni. Ezért Sorozhatónem az egyetlen mechanizmus a szerializáláshoz-deserializáláshoz Java-ban. Ma az Externalizálható felülettel ismerkedünk meg . Mielőtt azonban tanulmányozni kezdenénk, felmerülhet egy ésszerű kérdés: miért van szükségünk egy másik mechanizmusra? Serializableelvégezte a dolgát, és mit nem lehet szeretni az egész folyamat automatikus végrehajtásában? És az általunk vizsgált példák is egyszerűek voltak. Tehát mi a probléma? Miért van szükségünk más interfészre lényegében ugyanazokhoz a feladatokhoz? Az a tény, hogy ennek Serializableszámos hiányossága van. Néhányat felsorolunk közülük:
  1. Teljesítmény. A Serializablefelületnek számos előnye van, de a nagy teljesítmény nyilvánvalóan nem tartozik ezek közé.

    Az Externalizálható felület bemutatása - 2

    Először is, Serializable a belső megvalósítás nagy mennyiségű szolgáltatási információt és mindenféle ideiglenes adatot generál.

    Másodszor, Serializable a Reflection API-ra támaszkodik (most nem kell mélyen belemerülnie ebbe; ha érdekel, nyugodtan olvashat bővebben). Ezzel a lehetőséggel megteheti a Javaban lehetetlennek tűnő dolgokat: például megváltoztathatja a privát mezők értékét. A CodeGymnek van egy kiváló cikke a Reflection API-ról . Ott olvashatsz róla.

  2. Rugalmasság. Az interfész használatakor nem mi irányítjuk a szerializálási-deserializálási folyamatot Serializable.

    Egyrészt nagyon kényelmes, mert ha nem foglalkozunk különösebben a teljesítménnyel, akkor jónak tűnik, ha nem kell kódot írni. De mi van akkor, ha valóban hozzá kell adnunk néhány saját funkciónkat (alább bemutatunk egy példát) a szerializálási logikához?

    Alapvetően csak a kulcsszóval kell irányítanunk a folyamatot, transienthogy kizárjunk bizonyos adatokat. Ez az. Ez az egész eszköztárunk :/

  3. Biztonság. Ez az elem részben az előző tételből származik.

    Korábban nem sok időt töltöttünk ezen gondolkodni, de mi van akkor, ha az osztályod bizonyos információi nem mások kíváncsi szemének és fülének szólnak? Egy egyszerű példa egy jelszó vagy más személyes felhasználói adat, amelyre a mai világban egy rakás törvény vonatkozik.

    Ha használjuk Serializable, nem igazán tudunk mit kezdeni vele. Mindent úgy szerializálunk, ahogy van.

    De ha helyesen tesszük, titkosítanunk kell az ilyen típusú adatokat, mielőtt fájlba írnánk vagy hálózaton keresztül elküldenék. De Serializableezt nem teszi lehetővé.

Az Externalizálható felület bemutatása - 3Nos, lássuk végre, hogyan nézne ki az osztály, ha használnánk a Externalizablefelületet.

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 {

   }
}
Amint látja, jelentős változások vannak! A legfontosabb nyilvánvaló: az Externalizableinterfész implementálásakor két szükséges módszert kell végrehajtania: writeExternal()ésreadExternal(). Mint korábban említettük, a szerializálás és a deszerializálás felelőssége a programozót terheli. De most megoldhatja azt a problémát, hogy nincs kontroll a folyamat felett! Az egész folyamatot közvetlenül Ön programozza be. Ez természetesen sokkal rugalmasabb mechanizmust tesz lehetővé. Ezenkívül a biztonsággal kapcsolatos probléma megoldódott. Mint látható, osztályunkban van egy személyes adatmező, amelyet nem lehet titkosítatlanul tárolni. Most már könnyedén írhatunk olyan kódot, amely megfelel ennek a megkötésnek. Például hozzáadhatunk osztályunkhoz két egyszerű privát módszert az érzékeny adatok titkosításához és visszafejtéséhez. Az adatokat a fájlba írjuk, és titkosított formában kiolvassuk a fájlból. A többi adat úgy lesz kiírva és olvasva, ahogy van :) Ennek eredményeként az osztályunk valahogy így néz ki:

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;
   }
}
Két olyan módszert valósítottunk meg, amelyek ugyanazokat ObjectOutputés ObjectInputparamétereket használják, amelyekkel a leckében már találkoztunk Serializable. A megfelelő pillanatban titkosítjuk vagy visszafejtjük a szükséges adatokat, és a titkosított adatokat felhasználjuk objektumunk szerializálására. Lássuk, hogyan is néz ki ez a gyakorlatban:

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

   }
}
A encryptString()és decryptString()metódusokban kifejezetten konzolkimenetet adtunk hozzá, hogy ellenőrizzük, milyen formában kerül sor a titkos adatok írására és olvasására. A fenti kód a következő sort jelenítette meg: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh A titkosítás sikerült! A fájl teljes tartalma így néz ki: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Most próbáljuk meg a deszerializációs logikánkat használni.

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

   }
}
Nos, úgy tűnik, itt semmi sem bonyolult. Működnie kell! Futtatjuk és megkapjuk... Kivétel a "main" szálban java.io.InvalidClassException: UserInfo; nincs érvényes konstruktor Az Externalizálható felület bemutatása - 4 Hoppá! :( Úgy látszik, ez nem olyan egyszerű! A deserializációs mechanizmus kivételt dobott, és megkövetelte, hogy hozzunk létre egy alapértelmezett konstruktort. Vajon miért. A -val Serializablemegvoltunk egy nélkül is... :/ Itt egy újabb fontos árnyalattal találkoztunk. Serializableközötti különbség Externalizablenem csak a programozó „kibővített” hozzáférésében és a folyamat rugalmasabb irányításának képességében rejlik, hanem magában a folyamatban is. Mindenekelőtt a deszerializációs mechanizmusban .Serializable, egyszerűen lefoglalják a memóriát az objektumhoz, majd a rendszer kiolvassa az értékeket az adatfolyamból, és felhasználja az objektum mezőinek beállítására. Ha használjuk Serializable, akkor az objektum konstruktora nem kerül meghívásra! Minden munka reflexión keresztül történik (a Reflection API, amelyet röviden említettünk az utolsó leckében). ExternalizableA deserializációs mechanizmus más . Először az alapértelmezett konstruktort hívják meg. Csak ezután hívják meg a létrehozott UserInfoobjektum readExternal()metódusát. Felelős az objektum mezőinek beállításáért. Ezért minden Externalizableinterfészt megvalósító osztálynak rendelkeznie kell egy alapértelmezett konstruktorral . Adjunk hozzá egyet az osztályunkhoz UserInfo, és futtassuk újra a kódot:

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();
   }
}
Konzolkimenet: Paul Piper útlevéladatai UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Paul Piper útlevéladatai' } Most ez valami egészen más! Először a dekódolt karakterlánc titkos információkkal jelent meg a konzolon. Ezután a fájlból visszaállított objektum karakterláncként jelenik meg! Sikeresen megoldottunk tehát minden problémát :) A szerializálás és deszerializálás témája egyszerűnek tűnik, de, mint látod, a leckék hosszúak voltak. És még sok mindenre nem tértünk ki! Az egyes interfészek használata még mindig sok finomsággal jár. De hogy ne robbantsa fel az agyát a túl sok új információ, röviden felsorolok még néhány fontos pontot, és linkeket adok további olvasnivalókhoz. Szóval, mit kell még tudni? Először isSerializable , a szerializálás során (függetlenül attól, hogy vagy a -t használod Externalizable) figyelj a staticváltozókra. A használatakor Serializableezek a mezők egyáltalán nem sorosodnak (és ennek megfelelően az értékeik nem változnak, mivel a staticmezők az osztályhoz tartoznak, nem az objektumhoz). De amikor használodExternalizable, Ön maga irányítja a folyamatot, így technikailag sorozatossá teheti őket. De nem ajánljuk, mivel valószínűleg sok finom hibát okoz. Másodszor , figyelni kell a változókra is a módosítóval final. Amikor használod Serializable, a szokásos módon sorba rendeződnek és deszerializálódnak, de amikor használod Externalizable, lehetetlen egy finalváltozót deszerializálni ! Az ok egyszerű: minden finalmező inicializálódik az alapértelmezett konstruktor meghívásakor – ezután az értéke nem módosítható. Ezért a mezőkkel rendelkező objektumok sorosításához finalhasználja a szabvány által biztosított szerializálást Serializable. Harmadszor , ha öröklést használ, az összes leszármazott osztály örököl néhányatExternalizableosztálynak alapértelmezett konstruktorokkal is rendelkeznie kell. Itt van egy link egy jó cikkhez a szerializációs mechanizmusokról: A következő alkalomig! :)