Einführung in die Java-Serialisierung

1. Einleitung

Serialisierung ist die Umwandlung des Status eines Objekts in einen Bytestrom. Deserialisierung macht das Gegenteil. Anders ausgedrückt ist Serialisierung die Umwandlung eines Java-Objekts in einen statischen Strom (eine Folge) von Bytes, die dann in einer Datenbank gespeichert oder über ein Netzwerk übertragen werden können.

2. Serialisierung und Deserialisierung

Der Serialisierungsprozess ist instanzunabhängig, dh Objekte können auf einer Plattform serialisiert und auf einer anderen deserialisiert werden. Klassen, die zur Serialisierung berechtigt sind, müssen eine spezielle Markierungsschnittstelle Serializable implementieren .

Sowohl ObjectInputStream als auch ObjectOutputStream sind übergeordnete Klassen, die java.io.InputStream bzw. java.io.OutputStream erweitern . ObjectOutputStream kann primitive Typen und Diagramme von Objekten als Bytestrom in einen OutputStream schreiben . Diese Streams können anschließend mit ObjectInputStream gelesen werden .

Die wichtigste Methode in ObjectOutputStream ist:

public final void writeObject(Object o) throws IOException;

Welches nimmt ein serialisierbares Objekt und konvertiert es in eine Folge (Stream) von Bytes. Ebenso ist die wichtigste Methode in ObjectInputStream :

public final Object readObject() throws IOException, ClassNotFoundException;

Welches kann einen Strom von Bytes lesen und ihn zurück in ein Java-Objekt konvertieren. Dies kann dann auf das ursprüngliche Objekt zurückgesetzt werden.

Lassen Sie uns die Serialisierung mit einer Personenklasse veranschaulichen . Beachten Sie, dass statische Felder zu einer Klasse gehören (im Gegensatz zu einem Objekt) und nicht serialisiert werden . Beachten Sie außerdem, dass wir das Schlüsselwort transient verwenden können , um Klassenfelder während der Serialisierung zu ignorieren:

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 }

Der folgende Test zeigt ein Beispiel für das Speichern eines Objekts vom Typ Person in einer lokalen Datei und das Zurücklesen dieses Werts in:

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

Wir haben ObjectOutputStream verwendet, um den Status dieses Objekts mithilfe von FileOutputStream in einer Datei zu speichern . Die Datei „yourfile.txt“ wird im Projektverzeichnis erstellt. Diese Datei wird dann mit FileInputStream geladen . ObjectInputStream nimmt diesen Stream auf und konvertiert ihn in ein neues Objekt namens p2 .

Schließlich testen wir den Status des geladenen Objekts und er stimmt mit dem Status des ursprünglichen Objekts überein.

Beachten Sie, dass das geladene Objekt explizit in einen Personentyp umgewandelt werden muss.

3. Vorsichtsmaßnahmen bei der Java-Serialisierung

Es gibt einige Einschränkungen, die die Serialisierung in Java betreffen.

3.1. Vererbung und Zusammensetzung

Wenn eine Klasse die Schnittstelle java.io.Serializable implementiert, sind auch alle ihre Unterklassen serialisierbar. Wenn ein Objekt einen Verweis auf ein anderes Objekt hat, müssen diese Objekte die Serializable- Schnittstelle separat implementieren. Andernfalls wird eine NotSerializableException ausgelöst:

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

Wenn eines der Felder in einem serialisierbaren Objekt aus einem Array von Objekten besteht, müssen auch alle diese Objekte serialisierbar sein. Andernfalls wird eine NotSerializableException ausgelöst.

3.2. UID der Serienversion

Die JVM ordnet jeder serialisierbaren Klasse eine ( lange ) Versionsnummer zu . Es wird verwendet, um zu überprüfen, ob die gespeicherten und geladenen Objekte dieselben Attribute haben und somit bei der Serialisierung kompatibel sind.

Diese Nummer kann von den meisten IDEs automatisch generiert werden und basiert auf dem Klassennamen, seinen Attributen und den zugehörigen Zugriffsmodifikatoren. Änderungen führen zu einer anderen Nummer und können eine InvalidClassException verursachen .

Wenn eine serialisierbare Klasse keine serialVersionUID deklariert, generiert die JVM zur Laufzeit automatisch eine. Es wird jedoch dringend empfohlen, dass jede Klasse ihre serialVersionUID deklariert, da die generierte vom Compiler abhängig ist und daher zu unerwarteten InvalidClassExceptions führen kann .

3.3. Benutzerdefinierte Serialisierung in Java

Java gibt eine Standardmethode an, mit der Objekte serialisiert werden können. Java-Klassen können dieses Standardverhalten überschreiben. Benutzerdefinierte Serialisierung kann besonders nützlich sein, wenn Sie versuchen, ein Objekt mit einigen unserialisierbaren Attributen zu serialisieren. Dies kann erreicht werden, indem zwei Methoden innerhalb der Klasse bereitgestellt werden, die serialisiert werden sollen:

private void writeObject(ObjectOutputStream out) throws IOException;

und

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

Mit diesen Methoden können wir diese unserialisierbaren Attribute in andere Formen serialisieren, die serialisiert werden können:

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 }

Der folgende Komponententest testet diese benutzerdefinierte Serialisierung:

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

In diesem Code erfahren Sie, wie Sie einige unserialisierbare Attribute speichern, indem Sie die Adresse mit einer benutzerdefinierten Serialisierung serialisieren. Beachten Sie, dass wir die unserialisierbaren Attribute als vorübergehend markieren müssen, um die NotSerializableException zu vermeiden .

4. Fazit

In diesem kurzen Tutorial haben wir die Java-Serialisierung überprüft, wichtige Dinge besprochen und gezeigt, wie man eine benutzerdefinierte Serialisierung durchführt.

Wie immer ist der in diesem Tutorial verwendete Quellcode über GitHub verfügbar.