Назад Зміст Вперед

1.2. Серіалізація.

Серіалізація - це процес збереження стану об'єкта в електронних даних; 
десеріалізація - це процес відновлення об'єкта з цих байт. Java Serialization API надає стандартний механізм для створення серіалізованих об'єктів.

Серіалізація об'єкта - це здатність об'єкта зберігати повну копію його і будь-яких інших об'єктів на які він посилається, використовуючи потік виводу (наприклад, у зовнішній файл). Таким чином, об'єкт може бути відтворений з серіалізованої (збереженої) копії трохи пізніше, коли це буде потрібно.

Серіалізація об'єктів, як нова можливість введена в JDK 1.1, надає функцію для перетворення груп або окремих об'єктів, в потік бітів або масив байтів, для зберігання або передачі по мережі. Цей потік бітів або масив байтів, можна перетворити назад в об'єкти Java. Головним чином це відбувається автоматично завдяки класам ObjectInputStream і ObjectOutputStream. Програміст може вирішити реалізувати цю функцію, шляхом реалізації інтерфейсу Serializable при створенні класу.

Навіщо серіалізація потрібна?

Серіалізація - це процес запису стану об'єкта в байтовий потік. Вона зручна, коли потрібно зберегти стан вашої проrрами в області постійного зберігання, такому як файл. Пізніше ви можете відновити ці об'єкти, використовуючи процес дeceріалізаціі.

Серіалізация також необхідна в реалізації віддаленого виклику методів (Remote  Method Invocation RMI). RMI дозволяє об'єкту Java на одній машині звертатися до методу об'єкта Java на іншій машині. Об'єкт може бути застосований як аргумент цього віддаленого методу. Машина, яка  посилає об'єкт, серіалізує і передає його. Машина, яка приймає об'єкт, десеріалізує його.

Отже, серіализація надає наступні можливості:
  • Система зберігання об'єктів, тобто: збереження їх властивостей під зовнішній файл, на диск або в базу даних.
  • Система викликів віддалених процедур.
  • Система розподілу об'єктів.
  • Система ідентифікації змін змінних даних в часі.

Реалізація інтерфейсу Serializable

Засобами серіалізації може бути збережений і відновлений тільки об'єкт класу, що реалізує інтерфейс Serializable. В інтерфейсі Serializable не визначається ніяких членів. Він служить лише для того, щоб вказати, що клас може бути серіалізірований. Якщо клас серіалізуєтся, то серіалізіруются і все його підкласи.

Змінні, оголошені як transient, не зберігаються засобами серіалізації. Не зберігаються і статичні змінні.

Два основні класи, які допомагають реалізувати інтерфейс Serializable:
ObjectInputStream
ObjectOutputStream

Більше>Шилдт Г. - Java 8. Полное руководство - 2015>ст.761-768
Приклад 1. Приклад простого класу із серіалізацією
import java.io.*;
public class RandomClass implements Serializable {
 // Генерація random значення private static int r() {
        return (int)(Math.random() * 10);
 }
    private int data[];
    // Конструктор
public RandomClass() {
        datafile = new int[r()];
        for (int i=0; i<datafile.length; i++)
        datafile[i]=r();
 }
    public void printout() {
 System.out.println("This RandomClass has "+datafile.length+" random integers");
 for (int i=0; i<datafile.length; i++) {
        System.out.print(datafile[i]+":");
        System.out.println();
    }
}

У наведеному вище коді, створюється клас, який є серіалізованим, тому що «Промаркований» інтерфейсом серіалізації. Клас створює масив випадкових цілих чисел, коли створюється його примірник.

Наведений нижче код, показує можливість запису об'єктів в потік, використовуючи клас ObjectOutputStream. Програма має масив цілих чисел, але для серіалізації ми не повинні перебирати її внутрішні об'єкти. Інтерфейс Seriliazable піклується про це автоматично.

Приклад 2. Приклад серіалізації об'єктів для виведення у файл.
import java.io.*;
import java.util.*;
public class OutSerialize {
    public static void main (String args[]) throws IOException {
        RandomClass rc1 = new RandomClass();
        RandomClass rc2 = new RandomClass();
//створення ланцюга потоків з потоком введення об'єкта в кінці
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("objects.dat"));
        Date now = new Date(System.currentTimeMillis());
//java.util.* був імпортований для використання класу Date
        out.writeObject(now);
        out.writeObject(rc1);
        out.writeObject(rc2);
out.close();
        System.out.println("I have written:");
System.out.println("A Date object: "+now);
        System.out.println("Two Group of randoms");
rc1.printout();
rc2.printout();
 }
}

Код нижче демонструє можливості класу ObjectInputStream, який зчитує серіалізовані дані з зовнішнього файлу, в програму. Об'єкти зчитуються в тому ж порядку, в якому були записані в файл.

Приклад 3. Читання серіалізованих об'єктів або Десеріалізація
import java.io.*;
import java.util.*;
public class InSerialize {
 public static void main (String args[]) throws  IOException, ClassNotFoundException {
    ObjectInputStream in =  new ObjectInputStream (new FileInputStream("objects.dat"));
 Date d1 = (Date)in.readObject();
 RandomClass rc1 = (RandomClass)in.readObject();
    RandomClass rc2 = (RandomClass)in.readObject();
    System.out.println("I have read:");
    System.out.println("A Date object: "+d1);
    System.out.println("Two Group of randoms");
    rc1.printout();
rc2.printout();
 }
}

Майже всі класи Java можуть бути серіалізовані, включаючи класи AWT. Фрейм, який є вікном, містить набір графічних компонентів. Деякі об'єкти класів Java, не можуть бути серіалізовані, тому що містять дані, що посилаються на короткочасні ресурси операційних систем. Наприклад класи java.io.FileInputStream і java.lang.Thread. Якщо об'єкт містить посилання на несеріалізовані елементи, вся операція серіалізації зазнає невдачі і буде викинуто виключення NotSerializableException. Якщо який-небудь об'єкт посилається на посилання несеріалізованного об'єкта, то його можна серіалізувати, використовуючи ключове слово transient.

Приклад 4. Створення Серіалізованих об'єктів, використовуючи ключове слово transient
public class Sclass implements Serializable{
public transient Thread newThread;     
//пам'ятайте, що потік (потік паралельного виконання) за замовчуванням несеріалізований клас
    private String studentID;
    private int sum;
}

Безпека в Серіалізації

Серіалізация класу в Java, має на увазі передачу всіх його даних у зовнішній файл або базу даних через потік. Ми можемо обмежити дані, які будуть серіалізовані, коли того побажаємо.
Є два способи зробити це:
  1. Кожен параметр класу оголошений як перехідний, що не серіалізується (за замовчуванням всі параметри класу серіалізуються)
  2. Або кожен параметр класу, який ми хочемо серіалізувати, позначається тегом Externalizable (за замовчуванням ніякі параметри не серіалізуються).
Поле даних не буде серіалізоване за допомогою ObjectOutputStream, коли воно буде викликано для об'єкта, якщо це поле даних, даного об'єкта позначено як transient. Наприклад - private transient String password.

З іншого боку, для явного оголошення даних об'єкта як серіалізоване, ми повинні промаркувати клас як Externalizable, і реалізувати методи writeExternal і readExteranl для запису і читання даних цього об'єкта явно.

Висновок:

Ряд додаткових вимог і можливостей серіалізації, про які потрібно знати:

По-перше, потрібно сказати про обмеження, які стосуються внутрішніх об'єктів серіалізованих класів. Якщо клас складається з полів елементарних типів і String, то проблем немає. Але, якщо до складу класу A входить поле класу B, то клас B теж повинен бути серіалізованим, тобто задовольняти інтерфейс Serializable. Якщо це не так, то в процесі серіалізації виникне виключення NotSerializableException.

По-друге, в процесі серіалізації / десеріалізациі не беруть участі статичні поля класу, оскільки вони фактично не є полями об'єктів (екземплярів класу), а полями класу в цілому.

І, по-третє, потрібно враховувати вплив спадкування на серіалізацію. Нехай клас A оголошений, як серіалізований. На його базі створено клас B, для якого не вказано implements Serializable. І, нарешті, від B породжений клас C теж серіалізований. Тоді при серіалізації будуть збережені всі поля класів A і C, але не B.

Особливість серіалізації об'єктів використана в багатьох розподілених системах, як спосіб передачі даних. Але серіалізація розкриває приховані деталі, таким чином руйнуючи справжність абстрактних типів даних, що в свою чергу руйнує інкапсуляцію. У той же час приємно знати, що дані серіалізованого об'єкта, ті ж самі дані, що були у вихідному, оригінальному об'єкті. Це також чудова можливість для реалізації інтерфейсу ObjectInputValidation і перевизначення методу validateObject (), навіть якщо використовуються кілька рядків коду. Якщо об'єкт не знайдено, то ми можемо належним чином викинути виняток InvalidObjectException.

.