ObrázokPokiaľ sa zúčastňujete väčšieho projektu tak je možné, že skôr alebo neskôr narazíte na problém, ktorý je veľmi bežný. Chcete serializovať/deserializovať objekty. Teda prevádzať ich údaje v pamäti na prúd bajtov (niekedy znakov) a to tak, aby ste ich v inom čase alebo na inom mieste vedeli prečítať naspäť. Ako mnohé vyspelé jazyky, aj Java má pre tento problém hotové riešenie. Poďme sa na neho pozrieť.

Na to, aby sa vám podarila úspešná serializácia alebo deserializácia, potrebujete 2 veci. Potrebujete riadiaci objekt, pomocou ktorého spustíte a budete riadiť proces zápisu alebo čítania. A potrebujete dátový objekt, ktorý bude zapísaný a ktorý musí byť upravený, aby ho ten prvý vedel použiť. Ak máte tieto dve zložky, je to všetko, čo potrebujete a serializáciu máte vyriešenú.

V prípade, že nemáte na serializáciu nejaké špeciálne požiadavky, tak vám Java dodá prvý spomínaný objekt a veľmi pomôže s druhým. Je to mechanizmus, ktorý budem volať natívna serializácia, pretože je to štandardná serializácia, ktorú viete použiť pre bežné prípady. V takom prípade vám Java dodáva dve riadiace triedy a to ObjectOutputStream a ObjectInputStream. Čo sa týka prúdov (Stream) v Jave, tak sa tam s veľkou obľubou používa návrhový vzor Wrapper. Aj tieto dve spomínané triedy sú len obálky, do ktorých musíte vložiť nejaký iný vstupný alebo výstupný prúd. Môže to byť napríklad prúd vzniknutý po otvorení súboru.

ObjectOutputStream objectDeserializer = new ObjectOutputStream( new BufferedOutputStream(Files.newOutputStream(file)))) ;

Dôležité je, že tieto špeciálne prúdy majú dve párové metódy readObject(Object o) a writeObject(Object o). Tie potom viete použiť na postupný zápis/čítanie vašich objektov. A to by mohlo byť z pohľadu riadenia všetko. Ako ale majú vyzerať objekty, ktoré chcete zapisovať/čítať?

Objekt, ktorý má fungovať s natívnou serializáciou musí spĺňať 3 podmienky:

1. objekt musí mať public modifikátor prístupu,

2. musí implementovať rozhranie Serializable,

3. predkovia triedy (od ktorých je odvodená) musia spĺňať predchádzajúce dve podmienky.

Urobiť public triedu asi nie je žiaden problém. Čo znamená implementovať rozhranie Serializable? No, veľa nie. To rozhranie je totiž v podstate prázdne (k tomu, prečo v „podstate“ sa dostanem o chvíľu). Ide hlavne o to, že týmto rozhraním označíte triedu, že je serializovateľná. Okrem toho je dobré do tried doplniť takúto premennú:

private static final long serialVersionUID = 2045L;

Presná hodnota a modifikátor prístupu nie je až taký podstatný. To, čo musí byť zachované, je názov a typ (a modifikátor static). Na čo to slúži? Pomocou tejto premennej Java vie, či pri deserializácii číta rovnakú verziu objektu ako zapisovala. Ak sa číslo zhoduje, je to ten istý objekt, ak nie, tak môžete dostať výnimku InvalidClassException . Z toho tiež vyplýva, že pri zmene objektu, ktorý ma dopad na serializáciu, by ste mali upraviť aj túto hodnotu. Ako presne, to už je na vás. Pre Javu je podstatné len to, že to nebude rovnaká hodnota. A to by bolo pre dátový objekt všetko. Takto pripravený súbor už len stačí podhodiť predtým spomínaným triedam a malo by dôjsť k serializácii.

S predkami triedy to môže byť ale trochu komplikovanejšie. Ako som písal, tretia podmienka je, že by mal byť serializovateľný aj predok. Nie vždy sa to ale dá, a preto na to existuje riešenie. Podstatné je, aby mal predok bezparametrický verejný konštruktor. Java tak bude vedieť vytvoriť objekt takého typu, ale to ešte neznamená, že bude zapisovať/čítať jeho premenné. O to sa musíte postarať vy v odvodenej triede. Ako som písal vyššie, Serializable v podstate neobsahuje žiadne povinné metódy. To „v podstate“ som použil preto, že má akúsi podporu pre dve metódy, a to writeObject(ObjectOuputStream in) a readObject(ObjectInputStream out). Sú to private metódy a teda nejde tak úplne o štandardnú implementáciu rozhrania, ale pokiaľ ich do svojej triedy zapíšete, budú volané. Pomocou týchto metód potom viete zapísať údaje predkov do prúdu.

Tieto dve spomínané metódy sú tiež kľúčom k vlastnej serializácii. To je ten prípad, kedy sa rozhodnete prebrať riadenie serializácie do vlastných rúk a namiesto toho, aby ste jednotlivé premenné nechali Javu zapisovať samú, používate metódy ako readBoolean() a readFloat() a ich opaky ako writeBoolean() a writeFloat() na zapisovanie jednotlivých údajov. Takto ste schopní sa rozhodnúť, ktoré premenné a v akom poradí budú zapísané.

Ak sa na chvíľu vrátim k natívnej serializácii, tak je tu ešte jedna krátka téma. Predstavte si, že nechcete, aby bola nejaká premenná serializovaná. Ako to dosiahnete? Existuje na to magický modifikátor transient, ktorým musíte danú premennú označiť. Následne ju Java bude považovať za behovú/dočasnú informáciu a nebude ju ani zapisovať, ani čítať.

Ďalšia téma, na ktorú treba pri serializácii myslieť je, že pri čítaní pomocou metódy ObjectInputStream.readObject() dostávate odkaz na objektu typu Object. Vy ale potrebujete svoje doménové objekty, a preto musíte urobiť pretypovanie. V prípade, že ste si istý tým, čo vám pri deserializácii vypadne z tejto metódy, môžete rovno pretypovávať. Ak ste však zapísali pole objektov rôznych tried (pričom to pole bolo pole base objektov), tak to nemusí byť také jednoduché. V takom prípade sa dá použiť operátor instanceof alebo metoda Class.GetName() na zistenie presného typu a následné pretypovanie objektu.

Posledná špecialita serializácie je optimalizácia, ktorá je štandardne pri zápise zapnutá. Táto optimalizácia spôsobuje, že ak raz serializujete jeden objekt, a potom ho chcete v jednom behu zaserializovať znova, použije sa už pripravená prvá verzia. A teda, ak ste medzitým objekt zmenili, do súboru sa opäť zapíše jeho pôvodná verzia. Aby ste sa toho zbavili, musíte pred druhým zápisom volať metódu ObjectOutputStream.Reset(). Tá spôsobí, že sa pri druhý raz zapíše aktuálna verzia objektu.

Ako som písal v úvode, serializácia je veľmi bežný problém a je dosť možné, že sa s ním stretnete. Ak vynechám štandardný prípad zápisu do súboru, veľmi často sa používa pri komunikácii so sieťovými službami, kde sa objekty serializujú do XML alebo JSON a takto sa prenášajú po sieti pomocou niektorého protokolu na prenos súborov (sú to de-facto tiež súbory ale nie uložené na disku). Aj preto vám jej zvládnutie môže ušetriť množstvo práce a bolesť hlavy zo situácie, pri ktorej nerozumiete, ako k nej môže dochádzať (ako napríklad optimalizácia z posledného odseku).