Introduktion till Java Serialization

1. Introduktion

Serialisering är konvertering av ett objekts tillstånd till en byte-ström; deserialisering gör det motsatta. Med andra ord är serialisering konvertering av ett Java-objekt till en statisk ström (sekvens) av byte som sedan kan sparas i en databas eller överföras via ett nätverk.

2. Serialisering och deserialisering

Serialiseringsprocessen är instansoberoende, dvs objekt kan serieras på en plattform och deserialiseras på en annan. Klasser som är berättigade till serialisering måste implementera ett speciellt markörgränssnitt Serializable.

Både ObjectInputStream och ObjectOutputStream är klasser på hög nivå som utökar java.io.InputStream respektive java.io.OutputStream . ObjectOutputStream kan skriva primitiva typer och grafer av objekt till en OutputStream som en ström av byte. Dessa strömmar kan därefter läsas med ObjectInputStream .

Den viktigaste metoden i ObjectOutputStream är:

public final void writeObject(Object o) throws IOException;

Vilket tar ett serieiserbart objekt och konverterar det till en sekvens (ström) av byte. På samma sätt är den viktigaste metoden i ObjectInputStream :

public final Object readObject() throws IOException, ClassNotFoundException;

Som kan läsa en ström av byte och konvertera den tillbaka till ett Java-objekt. Detta kan sedan kastas tillbaka till det ursprungliga objektet.

Låt oss illustrera serialisering med en personklass . Observera att statiska fält tillhör en klass (i motsats till ett objekt) och inte är i serie . Observera också att vi kan använda nyckelordet transient för att ignorera klassfält under serialisering:

public class Person implements Serializable { private static final long serialVersionUID = 1L; static String country = "ITALY"; private int age; private String name; transient int height; // getters and setters }

Testet nedan visar ett exempel på hur man sparar ett objekt av typen Person i en lokal fil och läser sedan tillbaka detta värde i:

@Test public void whenSerializingAndDeserializing_ThenObjectIsTheSame() () throws IOException, ClassNotFoundException { Person person = new Person(); person.setAge(20); person.setName("Joe"); FileOutputStream fileOutputStream = new FileOutputStream("yourfile.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(person); objectOutputStream.flush(); objectOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("yourfile.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Person p2 = (Person) objectInputStream.readObject(); objectInputStream.close(); assertTrue(p2.getAge() == p.getAge()); assertTrue(p2.getName().equals(p.getName())); }

Vi använde ObjectOutputStream för att spara objektets tillstånd i en fil med FileOutputStream . Filen “yourfile.txt” skapas i projektkatalogen. Den här filen laddas sedan med FileInputStream. ObjectInputStream plockar upp den här strömmen och konverterar den till ett nytt objekt som heter p2 .

Slutligen testar vi tillståndet för det laddade objektet och det matchar tillståndet för det ursprungliga objektet.

Observera att det laddade objektet måste uttryckligen kastas till en Person- typ.

3. Varningar för Java-serialisering

Det finns några försiktighetsåtgärder som rör serialisering i Java.

3.1. Arv och sammansättning

När en klass implementerar java.io.Serialiserbart gränssnitt kan alla dess underklasser också serialiseras. Tvärtom, när ett objekt har en hänvisning till ett annat objekt, måste dessa objekt implementera Serializable- gränssnittet separat, annars kastas ett NotSerializableException :

public class Person implements Serializable { private int age; private String name; private Address country; // must be serializable too } 

Om ett av fälten i ett serialiserbart objekt består av en uppsättning objekt måste alla dessa objekt också kunna serienummeras, annars kastas ett NotSerializableException .

3.2. Seriell version UID

JVM kopplar ett versionsnummer ( långt ) till varje klass som kan serieiseras. Den används för att verifiera att de sparade och laddade objekten har samma attribut och därmed är kompatibla vid serialisering.

Detta nummer kan genereras automatiskt av de flesta IDE: er och baseras på klassnamnet, dess attribut och tillhörande åtkomstmodifierare. Alla ändringar resulterar i ett annat nummer och kan orsaka ett InvalidClassException .

Om en serialiserbar klass inte deklarerar en serialVersionUID genererar JVM en automatiskt vid körning. Det rekommenderas dock starkt att varje klass deklarerar sin serialVersionUID eftersom den genererade är kompilatorberoende och därmed kan resultera i oväntade InvalidClassExceptions .

3.3. Anpassad serialisering i Java

Java anger ett standard sätt på vilket objekt kan serieiseras. Java-klasser kan åsidosätta detta standardbeteende. Anpassad serialisering kan vara särskilt användbar när man försöker serieera ett objekt som har vissa attribut som inte kan serieras. Detta kan göras genom att tillhandahålla två metoder inom klassen som vi vill serieera:

private void writeObject(ObjectOutputStream out) throws IOException;

och

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;

Med de här metoderna kan vi serialisera de oserialiserbara attributen till andra former som kan serieseras:

public class Employee implements Serializable { private static final long serialVersionUID = 1L; private transient Address address; private Person person; // setters and getters private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); oos.writeObject(address.getHouseNumber()); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); Integer houseNumber = (Integer) ois.readObject(); Address a = new Address(); a.setHouseNumber(houseNumber); this.setAddress(a); } }
public class Address { private int houseNumber; // setters and getters }

Följande enhetstest testar denna anpassade serialisering:

@Test public void whenCustomSerializingAndDeserializing_ThenObjectIsTheSame() throws IOException, ClassNotFoundException { Person p = new Person(); p.setAge(20); p.setName("Joe"); Address a = new Address(); a.setHouseNumber(1); Employee e = new Employee(); e.setPerson(p); e.setAddress(a); FileOutputStream fileOutputStream = new FileOutputStream("yourfile2.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(e); objectOutputStream.flush(); objectOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("yourfile2.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Employee e2 = (Employee) objectInputStream.readObject(); objectInputStream.close(); assertTrue( e2.getPerson().getAge() == e.getPerson().getAge()); assertTrue( e2.getAddress().getHouseNumber() == e.getAddress().getHouseNumber()); }

I den här koden ser vi hur man sparar vissa oserialiserbara attribut genom att serialisera adress med anpassad serialisering. Observera att vi måste markera de oserialiserbara attributen som övergående för att undvika NotSerializableException.

4. Slutsats

I denna snabba handledning har vi granskat Java-serialisering, diskuterat viktiga saker att tänka på och har visat hur man gör anpassad serialisering.

Som alltid är källkoden som används i denna handledning tillgänglig på GitHub.