Introducere în serializarea Java

1. Introducere

Serializarea este conversia stării unui obiect într-un flux de octeți; deserializarea face contrariul. Declarat diferit, serializarea este conversia unui obiect Java într-un flux static (secvență) de octeți care poate fi apoi salvat într-o bază de date sau transferat printr-o rețea.

2. Serializare și Deserializare

Procesul de serializare este independent de instanță, adică obiectele pot fi serializate pe o platformă și deserializate pe alta. Clasele care sunt eligibile pentru serializare trebuie să implementeze o interfață specială de marcare Serializabilă.

Atât ObjectInputStream cât și ObjectOutputStream sunt clase de nivel înalt care extind java.io.InputStream și respectiv java.io.OutputStream . ObjectOutputStream poate scrie tipuri primitive și grafice de obiecte într-un OutputStream ca un flux de octeți. Aceste fluxuri pot fi ulterior citite folosind ObjectInputStream .

Cea mai importantă metodă în ObjectOutputStream este:

public final void writeObject(Object o) throws IOException;

Care ia un obiect serializabil și îl transformă într-o secvență (flux) de octeți. În mod similar, cea mai importantă metodă în ObjectInputStream este:

public final Object readObject() throws IOException, ClassNotFoundException;

Care poate citi un flux de octeți și îl poate converti înapoi într-un obiect Java. Aceasta poate fi apoi aruncată înapoi la obiectul original.

Să ilustrăm serializarea cu o clasă Person . Rețineți că câmpurile statice aparțin unei clase (spre deosebire de un obiect) și nu sunt serializate . De asemenea, rețineți că putem utiliza cuvântul cheie tranzitoriu pentru a ignora câmpurile clasei în timpul serializării:

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 }

Testul de mai jos prezintă un exemplu de salvare a unui obiect de tip Person într-un fișier local, apoi citiți această valoare înapoi în:

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

Am folosit ObjectOutputStream pentru salvarea stării acestui obiect într-un fișier folosind FileOutputStream . Fișierul „yourfile.txt” este creat în directorul proiectului. Acest fișier este apoi încărcat folosind FileInputStream. ObjectInputStream preia acest flux și îl convertește într-un nou obiect numit p2 .

În cele din urmă, testăm starea obiectului încărcat și se potrivește cu starea obiectului original.

Observați că obiectul încărcat trebuie să fie exprimat în mod explicit la un tip de persoană .

3. Avertismente de serializare Java

Există câteva avertismente care privesc serializarea în Java.

3.1. Moștenire și compoziție

Când o clasă implementează interfața java.io.Serializable , toate subclasele sale sunt, de asemenea, serializabile. Dimpotrivă, atunci când un obiect are o referință la un alt obiect, aceste obiecte trebuie să implementeze interfața Serializable separat, altfel va fi aruncată o NotSerializableException :

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

Dacă unul dintre câmpurile dintr-un obiect serializabil constă dintr-o matrice de obiecte, atunci toate aceste obiecte trebuie să fie și serializabile, altfel va fi aruncată o NotSerializableException .

3.2. UID versiune serial

JVM asociază un număr de versiune ( lung ) cu fiecare clasă serializabilă. Este folosit pentru a verifica dacă obiectele salvate și încărcate au aceleași atribute și, prin urmare, sunt compatibile la serializare.

Acest număr poate fi generat automat de majoritatea IDE-urilor și se bazează pe numele clasei, atributele sale și modificatorii de acces asociați. Orice modificare are ca rezultat un număr diferit și poate provoca o excepție InvalidClassException .

Dacă o clasă serializabilă nu declară un serialVersionUID , JVM va genera unul automat în timpul rulării. Cu toate acestea, este foarte recomandat ca fiecare clasă să-și declare serialVersionUID deoarece cel generat este dependent de compilator și, prin urmare, poate duce la InvalidClassExceptions neașteptate .

3.3. Serializare personalizată în Java

Java specifică un mod implicit în care obiectele pot fi serializate. Clasele Java pot suprascrie acest comportament implicit. Serializarea personalizată poate fi deosebit de utilă atunci când încercați să serializați un obiect care are unele atribute neserializabile. Acest lucru se poate face oferind două metode în clasă pe care dorim să le serializăm:

private void writeObject(ObjectOutputStream out) throws IOException;

și

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

Cu aceste metode, putem serializa acele atribute neserializabile în alte forme care pot fi serializate:

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 }

Următorul test de unitate testează această serializare personalizată:

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

În acest cod, vedem cum se salvează unele atribute neserializabile prin serializarea Adresă cu serializare personalizată. Rețineți că trebuie să marcăm atributele neserializabile ca tranzitorii pentru a evita NotSerializableException.

4. Concluzie

În acest tutorial rapid, am analizat serializarea Java, am discutat lucruri importante de reținut și am arătat cum să facem serializarea personalizată.

Ca întotdeauna, codul sursă utilizat în acest tutorial este disponibil pe GitHub.