Serializace je v Javě velice mocný nástroj. Bohužel s ní přicházejí i problémy. Pojďme se na ni v následujícím článku podívat blíže.
Serializace je v Javě velice mocný nástroj. Dovoluje nám ukládat a pracovat s celými objekty, jejihž ukládání do různých „streamů“ necháváme přímo na Javě. Bohužel s ní přicházejí problémy mezi které například patří nekompatibilita jednotlivých tříd v případě, že zapomeneme vygenerovat, či napsat serialVersionUID. V tomto článku si lehce shrneme základy serializace a poté se zaměříme na problém, který může vzniknout při jejím použití.
Co je tedy serializace? Je to proces, který z libovolného objektu vytvoří sekvenci bytů a tuto sekvenci opět dokáže přetvořit do objektu. Daný objekt samozřejmě musí implementovat rozhraní Serializable. K tomuto rozhraní se váže již zmiňované serialVersionUID, které slouží k rozpoznání dané třídy při serializaci / deserializaci. Toto číslo (long) si můžete zvolit vlastní, nebo ho nechat vygenerovat. Generování probíhá na základě hashe založeného převážně na fieldech dané třídy. Pokud toto číslo neuvedeme, je vygenerováno automaticky při buildu třídy. Na následující ukázce máme jednoduchý příklad třídy, která by měla jít bez problému serializovat a deserializovat.
public class MujObjekt implements Serializable { private static final long serialVersionUID = 6985898950123971684L; private String prvniField = null; private String druhyField = null; transient private String tretiField = null; ... }
Na ukázce si táké můžeme všimnout, že jsme jeden field označili jako transient. Tímto označením můžeme určovat, jaké fieldy budou serializovány a jaké nikoliv.
Další ukázka kódu představuje samotný proces do serializace / deserializace do souborového streamu. Serializovat samozřejmě nemusíme jenom do souboru, ale lze využít jakýkoliv stream.
public MujObjekt readSerializedFile() throws Exception { MujObjekt mujObjekt = null; ObjectInputStream in = new ObjectInputStream(new FileInputStream(FILE_NAME)); mujObjekt = (MujObjekt)in.readObject(); return mujObjekt; } public void makeSerializedFile() throws Exception { MujObjekt mujObjekt = new MujObjekt(); ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(FILE_NAME)); os.writeObject(mujObjekt); os.close(); }
Ne všechny třídy lze samozřejmě serializovat. Mezi ty, které nejdou patří například Thread, Socket a další.
Serializaci objektu lze také zakázat – stačí implementovat metody writeObject a readObject a umístit do nich vhodný kód. Zde malé upozornění – obě metody musí být označeny jako private:
public class MujObjekt implements Serializable { ... private void writeObject(ObjectOutputStream out) throws IOException { throw new NotSerializableException("MujObjekt nelze serializovat"); } private void readObject(ObjectInputStream in) throws IOException { throw new NotSerializableException("MujObjekt nelze deserializovat"); } ... }
Problém nastává, když jsme zapomněli uvést serialVersionUID, přidali jsme field do naší třídy a nyní ji potřebujeme desrializovat z uloženého streamu. V tuto chvíli jsou naše třídy nekompatibilní, protože třída, která je serializovaná v souboru má jiné vygenerované UID než třída nová.
Z tohoto problému lze lehce „vybruslit“ pomocí nástroje serialver, který je dodáván přímo s JDK. Pomocí něho snadně zjistíme UID již zkompilované třídy. Použití je následující:
serialver -classpath c:\jar_file.jar cz.morosystems.MujObjekt
Číslo, které nám vrátí, pak doplníme do nové třídy a ta se stává okamžitě kompatibilní s již uloženou třídou. Samozřejmě je vždy rychlejší a bezpečnější pamatovat si, že ke každé třídě, která implementuje rozhraní Serializable je dobré vytvořit i serialVersionUID.
Vybráno z komentářů
Možné problémy
Cituju: ‚Číslo, které nám vrátí, pak doplníme do nové třídy a ta se stává okamžitě kompatibilní s již uloženou třídou.‘
Clanek je podle me hodne zavadejici. Nastavenim serialVersionUID trida nezacne byt kompatibilni s predchozi verzi. Timto krokem jenom umlcime serializator a zakazeme mu varovat nas pred pripadnymy problemy. Bylo by hezke kdyby druhy dil rozvedl proc vlastne tahle kontrola existuje a jake problemy mohou vzniknout kdyz ji vypneme.
Autori serializace prece nepridali serialVersionUID jen proto aby komplikovali praci vyvojarum. Tahle kontrola ma svuj vyznam a pokud ji vypneme, tak si zadame o dalsi zakernejsi problemy…
Pokud nastavime serialVersionUID tak bysme meli vedet ze je nutne jej obcas zmenit. Radeji bych nepouzival bych vygenerovane cislo (z nastroje serialver). Zacal cislovat verze od 1 a zvysoval je – pak je jasnejsi co serialVersionUID vyjadruje a neni to nejake magicke cislo nastavene na podivnou hodnotu…
Odpověď na možné problémy
Nechtěl jsem samozřejmě nikoho zmást a říci, že serialVersionUID je nějaké čertovo číslo. Článek popisuje problém, který může nastat, pokud potřebujeme třídu „zkompatibilnit“ s již existující třídou, kterou nemáme možnost změnit a která je někde serializována. Toť celé.
To, že je serialVersionUID převážně k oddělení různých verzí tříd a právě k zábránění nekompatibility mezi nimi, nechme třeba opravdu na další článek.
Poslední komentáře