java:object_serialization

Object Serialization/Deserialization

Η Java παρέχει την δυνατότητα μετατροπής ενός αντικειμένου σε μία σειρά από bytes, προκειμένου αυτό στη συνέχεια να αποθηκευτεί σε ένα μέσω μόνιμης αποθήκευσης (π.χ. αρχείο στο filesystem) ή να μεταδοθεί μέσω δικτύου προκειμένου να δημιουργηθεί ένα αντίγραφο του σε απομακρυσμένο σημείο. Η διαδικασία μετατροπής των αντικειμένων σε bytes ονομάζεται object serialization.

Αφού ένα αντικείμενο μετατραπεί σε σειρά από bytes μπορούμε να ακολουθήσουμε την αντίστροφη διαδικασία προκειμένου να δημιουργήσουμε ένα αντίγραφο του αντικειμένου. Η διαδικασία “σειριοποίησης” ενός αντικειμένου είναι ανεξάρτητη του JVM που χρησιμοποιούμε πράγμα που σημαίνει ότι ένα αντικείμενο που μεταδίδεται μέσω δικτύου μπορεί να αναπαραχθεί στη μνήμη σε μορφή αντικειμένου σε άλλο JVM/υπολογιστή.

Οι κλάσεις ObjectInputStream και ObjectOutputStream αποτελούν κλάσεις τύπου InputStream και OutputStream υψηλού επιπέδου οι οποίες επιτελούν την διαδικασία της “σειριοποίησης” και “αποσειριοποίησης” των αντικειμένων.

Από τις μεθόδους της κλάσης ObjectOutputStream μας ενδιαφέρει η μέθοδος writeObject η οποία μετατρέπει σε σειρά από bytes το αντικείμενο obj

public final void writeObject(Object obj) throws IOException
Parameters:
    obj - the object to be written
Throws:
    InvalidClassException - Something is wrong with a class used by serialization.
    NotSerializableException - Some object to be serialized does not implement the java.io.Serializable interface.
    IOException - Any exception thrown by the underlying OutputStream.

Αντίστοιχα από τις μεθόδους της κλάσης ObjectInputStream μας ενδιαφέρει η μέθοδος readObject η οποία μετατρέπει σε αντικείμενο μία σειρά από bytes.

public final Object readObject() throws IOException, ClassNotFoundException
Returns:
    the object read from the stream
Throws:
    ClassNotFoundException - Class of a serialized object cannot be found.
    InvalidClassException - Something is wrong with a class used by serialization.
    StreamCorruptedException - Control information in the stream is inconsistent.
    OptionalDataException - Primitive data was found in the stream instead of objects.
    IOException - Any of the usual Input/Output related exceptions.

Απαραίτητη προϋπόθεση για το serialization/deserialization των αντικειμένων είναι

  1. η κλάση του αντικειμένου να υλοποιεί το interface Serializable.
  2. όλα τα πεδία της κλάσης να υλοποιούν το interface Serializable. Εάν υπάρχουν πεδία που δεν το υλοποιούν τότε αυτά θα πρέπει να δηλωθούν ως transient. Η δήλωση transient μπροστά από ένα πεδίο υποδεικνύει ότι αυτό δεν θα συμπεριληφθεί στη διαδικασία του serialization/deserialization.

Όλοι οι βασικοί (primitive) τύποι δεδομένων κατά την διαδικασία του serialization μετατρέπονται σε αναφορικούς τύπους. Οι αναφορικοί τύποι Integer, Long, Double, Float, Character, Boolean και String είναι όλοι τύποι δεδομένων που υποστηρίζουν serialization.

Παράδειγμα Serialization

Θα επιχειρήσουμε να κάνουμε serialize ένα αντικείμενο της παρακάτω κλάσης Employee.

Employee.java
iimport java.util.*;
 
public class Employee implements java.io.Serializable {
   public String name;
   public String address;
   public int AMKA;
   public double salary;
   public Employee next;
   public ArrayList<Employee> list;
 
   public String toString() {
      String str = "Name: " + name+"\n";
      str+="Address: " + address+"\n";
      str+="AMKA: " + AMKA+"\n";
      str+="Salary: " + salary+"\n";
      if(next != null)
        str+="Next: " + next.name+"\n";
 
      if( list != null) {
        Iterator<Employee> it = list.iterator();
        if( it.hasNext() ) {
          str+="List: ";
        }
        while( it.hasNext() ) {
          str+= it.next().name+", ";
        }
        str+="\n";
      }
      return str;
   }
}

Ας υποθέσουμε ότι κατασκευάζουμε δύο τέτοια αντικείμενα τα οποία τα μετατρέπουμε σε μία σειρά από bytes και τα αποθηκεύουμε στο αρχείο employees.ser (υποθέτουμε ότι έχετε την δυνατότητα να δημιουργήσετε το αρχείο στον τρέχοντα κατάλογο). Ο κώδικας μετατροπής και αποθήκευσης δίνεται παρακάτω:

SerializeDemo.java
import java.io.*;
import java.util.*;
public class SerializeDemo {
 
   public static void main(String [] args) {
      Employee e = new Employee();
      e.name = "Vana Doufexi";
      e.address = "Gklavani 37, Volos";
      e.AMKA = 11122333;
      e.salary = 1000.9999;
 
      Employee e1 = new Employee();
      e1.name = "George Thanos";
      e1.address = "28hs Septembriou & Glavani, Volos";
      e1.AMKA = 11122999;
      e1.salary = 458.1234;
 
      e.next = e1;
      e1.next = e;
 
      e.list = new ArrayList<>();
      e.list.add(e);
      e.list.add(e1);
 
      e1.list = new ArrayList<>();
      e1.list.add(e);
      e1.list.add(e1);
 
      try {
         FileOutputStream fileOut =
         new FileOutputStream("./employees.ser");
         ObjectOutputStream out = new ObjectOutputStream(fileOut);
         out.writeObject(e);
         out.writeObject(e1);
         out.close();
         fileOut.close();
         System.out.printf("Serialized data is saved in /tmp/employee.ser");
      }catch(IOException ex) {
         ex.printStackTrace();
      }
   }
}

Μεταγλωττίστε και τρέξτε το πρόγραμμα και επιβεβαιώστε ότι δημιουργήθηκε το αρχείο employees.ser.

Παράδειγμα Deserialization

Ας υποθέσουμε ότι αφού αποθηκεύσαμε τα αντικείμενα στο αρχείο employees.ser θέλουμε να τα ανακτήσουμε ως λειτουργικά αντικείμενα σε ένα νέο πρόγραμμα. Ας δούμε το πρόγραμμα αυτό, το οποίο εκτυπώνει το περιεχόμενο τους με την βοήθεια της μεθόδου toString().

DeserializeDemo.java
import java.io.*;
public class DeserializeDemo {
 
   public static void main(String [] args) {
      Employee e, e1;
      try {
         FileInputStream fileIn = new FileInputStream("employees.ser");
         ObjectInputStream in = new ObjectInputStream(fileIn);
         e = (Employee) in.readObject();
         e1 = (Employee) in.readObject();
         in.close();
         fileIn.close();
      }catch(IOException i) {
         i.printStackTrace();
         return;
      }catch(ClassNotFoundException ex) {
         System.out.println("Employee class not found");
         ex.printStackTrace();
         return;
      }
 
      System.out.println(e);
      System.out.println(e1);      
   }
}

Στο παραπάνω παράδειγμα δηλώστε τα τελευταία τρία πεδία της κλάσης Employee ως transient, oπως παρακάτω. Μεταγλωττίστε και τρέξτε ξανά τις κλάσεις SerializeDemo και DeserializeDemo. Τι παρατηρείτε;

Employee.java
import java.util.*;
 
public class Employee implements java.io.Serializable {
   public String name;
   public String address;
   public int AMKA;
   public transient double salary;
   public transient Employee next;
   public transient ArrayList<Employee> list;
 
   public String toString() {
      String str = "Name: " + name+"\n";
      str+="Address: " + address+"\n";
      str+="AMKA: " + AMKA+"\n";
      str+="Salary: " + salary+"\n";
      if(next != null)
        str+="Next: " + next.name+"\n";
 
      if( list != null) {
        Iterator<Employee> it = list.iterator();
        if( it.hasNext() ) {
          str+="List: ";
        }
        while( it.hasNext() ) {
          str+= it.next().name+", ";
        }
        str+="\n";
      }
      return str;
   }
}
java/object_serialization.txt · Last modified: 2017/02/17 14:11 by gthanos