Java serialVersionUID – Sådan genereres serialVersionUID
Java-serialisering er processen med at konvertere et objekt til en strøm af bytes, så vi kan gøre ting som at gemme det på disk eller sende det over netværket. Deserialisering er den omvendte proces - at konvertere en strøm af bytes til et objekt i hukommelsen.
Under serialisering knytter java runtime et versionsnummer til hver serialiserbar klasse. Dette nummer kaldes serialVersionUID , som bruges under deserialisering til at verificere, at afsenderen og modtageren af et serialiseret objekt har indlæst klasser for det objekt, der er kompatible med hensyn til serialisering. Hvis modtageren har indlæst en klasse for objektet, der har en anden serialVersionUID
end den tilsvarende afsenderens klasse, vil deserialisering resultere i en InvalidClassException
.
1. Java serialVersionUID-syntaks
En serialiserbar klasse kan erklære sin egen serialVersionUID eksplicit ved at erklære et felt med navnet "serialVersionUID
”, der skal være statisk, endelig og af typen lang.
private static final long serialVersionUID = 4L;
Her repræsenterer serialVersionUID klasseversionen, og vi bør øge den, hvis den nuværende version af din klasse er ændret, så den ikke længere er bagudkompatibel med dens tidligere version.
2. Eksempel på Java-serialisering og deserialisering
Lad os se et eksempel på, hvordan en klasse serialiseres og derefter deserialiseres.
package com.howtodoinjava.demo.serialization; import java.io.*; import java.util.logging.Logger; public class DemoClass implements java.io.Serializable { private static final long serialVersionUID = 4L; //Default serial version uid private static final String fileName = "DemoClassBytes.ser"; //Any random name private static final Logger logger = Logger.getLogger(""); //Few data fields //Able to serialize private static String staticVariable; private int intVariable; //Not able to serialize transient private String transientVariable = "this is a transient instance field"; private Thread threadClass; public static void main(String[] args) throws IOException, ClassNotFoundException { //Serialization DemoClass test = new DemoClass(); test.intVariable = 1; staticVariable = "this is a static variable"; writeOut(test); System.out.println("DemoClass to be saved: " + test); //De-serialization System.out.println("DemoClass deserialized: " + readIn()); } private static Object readIn() throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(fileName))); return ois.readObject(); } private static void writeOut(java.io.Serializable obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(fileName))); oos.writeObject(obj); oos.close(); } @Override public String toString() { return "DemoClass: final static fileName=" + fileName + ", final static logger=" + logger + ", non-final static staticVariable=" + staticVariable + ", instance intVariable=" + intVariable + ", transient instance transientVariable=" + transientVariable + ", non-serializable instance field threadClass:=" + threadClass; } }
Programoutput.
DemoClass to be saved: DemoClass: final static fileName=DemoClassBytes.ser, final static logger=java.util.logging.LogManager$RootLogger@1d99a4d, non-final static staticVariable=this is a static variable, instance intVariable=1, transient instance transientVariable=this is a transient instance field, non-serializable instance field threadClass:=null //Execute readIn() function from a separate main() method //to get given below output correctly. It will flush out the static fields. DemoClass deserialized: DemoClass: final static fileName=DemoClassBytes.ser, final static logger=java.util.logging.LogManager$RootLogger@cd2c3c, non-final static staticVariable=null, instance intVariable=1, transient instance transientVariable=null, non-serializable instance field threadClass:=null
Hvis en serialiserbar klasse ikke eksplicit erklærer en serialVersionUID
, så vil serialiseringens køretid beregne en standard serialVersionUID
værdi for den pågældende klasse baseret på forskellige aspekter af klassen.
3. Sådan genereres serialVersionUID
Joshua Bloch siger i Effective Java, at det automatisk genererede UID er genereret baseret på et klassenavn, implementerede grænseflader og alle offentlige og beskyttede medlemmer. Ændring af nogen af disse på nogen måde vil ændre serialVersionUID.
Det anbefales dog kraftigt, at alle klasser, der kan serialiseres, eksplicit erklærer serialVersionUID-værdier, da standarden serialVersionUID
beregning er meget følsom over for klassedetaljer, der kan variere afhængigt af compilerimplementeringer og kan producere forskellige serialVersionUID i forskellige miljøer . Dette kan resultere i uventet InvalidClassException under deserialisering.
Derfor for at garantere en konsistent serialVersionUID værdi på tværs af forskellige java compiler implementeringer, skal en serialiserbar klasse erklære en eksplicit serialVersionUID
værdi. Det anbefales også kraftigt, at eksplicit serialVersionUID
erklæringer bruger private
modifikator i serialVersionUID hvor det er muligt, da sådanne erklæringer kun gælder for den umiddelbart indberettende klasse.
Bemærk også, at serialVersionUID
feltet er ikke nyttigt som nedarvet medlem.
Baseret på min korte karriere kan jeg sige, at lagring af serialiserede data i lang tid [spatial serialisering ] er ikke særlig almindelig brug. Det er langt mere almindeligt at bruge serialiseringsmekanismen til midlertidigt at skrive data [temporal serialization ] til for eksempel en cache eller send den over netværket til et andet program for at bruge informationen.
I sådanne tilfælde er vi ikke interesserede i at opretholde bagudkompatibilitet. Vi er kun optaget af at sikre, at de kodebaser, der kommunikerer på netværket, faktisk har den samme version af relevante klasser. For at lette en sådan kontrol skal vi vedligeholde serialVersionUID lige som det og ikke ændre det. Glem heller ikke at opdatere det, når du laver inkompatible ændringer af dine klasser, på begge sider af applikationer på netværket.
4. Java-klasser uden serialVersionUID
Det er ikke den situation, vi nogensinde ønsker at stå over for. Men det er virkeligheden, og nogle gange sker det (skal jeg sige sjældent?). Hvis vi har brug for at ændre en sådan klasse på en inkompatibel måde, men ønsker at bevare serialiserings-/deserialiseringskapaciteten med den gamle version af klassen, kan vi bruge JDK-værktøjet "serialver". Dette værktøj genererer serialVersionUID på den gamle klasse , og indstil det eksplicit på den nye klasse. Glem ikke at implementere readObject()
og writeObject()
metoder, fordi den indbyggede deserialiseringsmekanisme (in.defaultReadObject()
) vil nægte at deserialisere fra gamle versioner af dataene.
Hvis vi definerer vores egen readObject()
funktion som kan læse gamle data tilbage. Denne brugerdefinerede kode bør kontrollere serialVersionUID
for at vide, hvilken version dataene er i og beslutte, hvordan de skal deserialiseres. Denne versioneringsteknik er nyttig, hvis vi gemmer serialiserede data, som overlever flere versioner af din kode.
Læs mere:Java-serialiseringskompatible og inkompatible ændringer
5. Java serialVersionUID – Resumé
transient
ogstatic
felter ignoreres i serialisering. Efter deserialiseringtransient
felter og ikke-endelige statiske felter vil være nul.final
ogstatic
felter har stadig værdier, da de er en del af klassedataene.ObjectOutputStream.writeObject(obj)
ogObjectInputStream.readObject()
bruges i serialisering og deserialisering.- Under serialisering skal vi håndtere
IOException
; under deserialisering skal vi håndtereIOException
ogClassNotFoundException
. Så den deserialiserede klassetype skal være i klassestien. - Uinitialiserede, ikke-serialiserbare, ikke-forbigående forekomstfelter tolereres.
Når du tilføjer "
private Thread th;
“, ingen fejl i serialiserbar. Men "private Thread threadClass = new Thread();
” vil forårsage undtagelse:Exception in thread "main" java.io.NotSerializableException: java.lang.Thread at java.io.ObjectOutputStream.writeObject0(Unknown Source) at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source) at java.io.ObjectOutputStream.writeSerialData(Unknown Source) at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source) at java.io.ObjectOutputStream.writeObject0(Unknown Source) at java.io.ObjectOutputStream.writeObject(Unknown Source) at com.howtodoinjava.demo.serialization.DemoClass.writeOut(DemoClass.java:42) at com.howtodoinjava.demo.serialization.DemoClass.main(DemoClass.java:27)
- Serialisering og deserialisering kan bruges til at kopiere og klone objekter. Den er langsommere end almindelig klon, men kan producere en dyb kopi meget nemt.
- Hvis jeg har brug for at serialisere en
Serializable
klasseEmployee
, men en af dens superklasser kan ikke serialiseres, kanEmployee
klasse stadig serialiseres og deserialiseres? Svaret er ja, forudsat at den ikke-serialiserbare superklasse har en no-arg-konstruktør, som påkaldes ved deserialisering for at initialisere denne superklasse. - Vi skal være forsigtige, når vi ændrer en klasse, der implementerer
java.io.Serializable
. Hvis klassen ikke indeholder enserialVersionUID
felt, dets serialVersionUID vil automatisk blive genereret af compileren.Forskellige compilere eller forskellige versioner af den samme compiler vil generere potentielt forskellige værdier.
- Beregning af
serialVersionUID
er baseret på ikke kun felter, men også på andre aspekter af klassen, såsom implement-klausul, konstruktører osv. Så den bedste praksis er eksplicit at erklære enserialVersionUID
felt for at opretholde bagudkompatibilitet. Hvis vi har brug for at ændre den serialiserbare klasse væsentligt og forventer, at den er inkompatibel med tidligere versioner, så skal vi øge serialVersionUID for at undgå at blande forskellige versioner.
God læring !!