Иллюстрированный самоучитель по Java

         

Сериализация объектов


Методы классов ObjectlnputStream и ObjectOutputStream позволяют прочитать из входного байтового потока или записать в выходной байтовый поток данные сложных типов — объекты, массивы, строки — подобно тому, как методы классов Datainputstream и DataOutputstream читают и записывают данные простых типов.

Сходство усиливается- тем, Что классы Objeetxxx содержат методы как для чтений, так и записи простых типов. Впрочем, эти методы предназначены не для использования в программах, а для записи/чтения полей объектов и элементов массивов.

Процесс записи объекта в выходной поток получил название

сериализации

(serialization), а чтения объекта из входного потока и восстановления его в оперативной памяти —

десериализации

(deserialization).

Сериализация объекта нарушает его безопасность, поскольку зловредный процесс может сериализовать объект в массив, переписать некоторые элементы массива, представляющие private-поля объекта, обеспечив себе, например, доступ к секретному файлу, а затем десериализовать объект с измененными полями и совершить с ним недопустимые действия.

Поэтому сериализации можно подвергнуть не каждый объект, а только тот, который реализует интерфейс seriaiizabie. Этот интерфейс не содержит ни полей, ни методов. Реализовать в нем нечего. По сути дела запись

class A implements Seriaiizabie{...}

это только пометка, разрешающая сериализацию класса А.

Как всегда в Java, процесс сериализации максимально автоматизирован. Достаточно создать объект класса ObjectOutputStream, связав его с выходным потоком, и выводить в этот поток объекты методом writeObject():

MyClass me = new MyClass("abc",

-12,

5.67e-5);

int[] arr = {10, 20, 30};



ObjectOutputStream oos = new ObjectOutputStream(

new FileOutputStream("myobjects.ser")) ; 

oos.writeObject(me); 

oos.writeObject(arr); 

oos.writeObject("Some string"); 

oos.writeObject (new Date()); 

oos.flush();

В выходной поток выводятся все нестатические поля объекта, независимо от прав доступа к ним, а также сведения о классе этого объекта, необходимые для его правильного восстановления при десериализации. Байт-коды методов класса не сериализуются.


Если в объекте присутствуют ссылки на другие объекты, то они тоже сериализуются, а в них могут быть ссылки на другие объекты, которые опять-таки сериализуются, и получается целое множество причудливо связанных между собой сериализуемых объектов. Метод writeObjecto распознает две ссылки на один объект и выводит его в выходной поток только один раз. К тому же, он распознает ссылки, замкнутые в кольцо, и избегает зацикливания.

Все классы объектов, входящих в такое сериализуемое множество, а также все их внутренние классы, должны реализовать интерфейс seriaiizabie, в противном случае будет выброшено исключение класса NotseriaiizabieException и процесс сериализации прервется. Многие классы J2SDK реализуют этот интерфейс. Учтите также, что все потомки таких классов наследуют реализацию. Например, класс java.awt.Component реализует интерфейс Serializable, значит, все графические компоненты можно сериализовать. Не реализуют этот интерфейс обычно классы, тесно связанные с выполнением программ, например, java.awt.Toolkit. Состояние экземпляров таких классов нет смысла сохранять или передавать по сети. Не реализуют интерфейс Serializable и классы, содержащие внутренние сведения Java "для служебного пользования".

Десериализация происходит так же просто, как и сериализация:

ObjectlnputStream ois = new ObjectInputStream(

new FilelnputStream("myobjects.ser")); 

MyClass mcl = (MyClass)ois.readObject(); 

int[] a = (int[])ois.readObject(); 

String s = (String)ois.readObject(); 

Date d = (Date)ois.readObject() ;

Нужно только соблюдать порядок чтения элементов потока. В листинге 18.6 мы создаем объект класса GregorianCaiendar с текущей датой и временем, сериализуем его в файл date.ser, через три секунды десериа-лизуем и сравниваем с текущим временем. Результат показан на рис. 18.7.



Листинг 18.6.


Сериализация объекта

import java.io.*; 

import java.util.*;

class SerDatef

public static void main(String[] args) throws Exception{



GregorianCaiendar d - new GregorianCaiendar(); 

QbjectOutputStream oos = new ObjectOutputStream{

new FileOutputStream("date.ser")); 

oos.writeObject(d); 

oos.flush(); 

oos.close();

Thread.sleep(3000);

ObjectlnputStream ois = new ObjectlnputStream(

new FileInputStream("date.ser"));

GregorianCaiendar oldDate = (GregorianCaiendar)ois.readObject(); 

ois.close();

GregorianCaiendar newDate = new GregorianCaiendar();

System.out.println("Old time = " +

oldDate.get(Calendar.HOUR) + ":" +

oldDate.get(Calendar.MINUTE) +":" + 

oldDate.get(Calendar.SECOND) +"\nNew time = " + 

newDate.get(Calendar.HOUR) +":" + 

newDate.get(Calendar.MINUTE) +":" + 

newDate.get(Calendar.SECOND)); 



}





Рис.18.7.


Сериализация  объекта

Если не нужно сериализовать какое- то поле, то достаточно пометить его служебным словом transient, например:

transient MyClass me = new MyClass("abc", -12, 5.67e-5);

Метод writeObjecto не записывает в выходной поток поля, помеченные static и transient. Впрочем, это положение можно изменить, переопределив метод writeObjecto или задав список сериализуемых полей.

Вообще процесс сериализации можно полностью настроить под свои нужды, переопределив методы ввода/вывода и воспользовавшись вспомогательными классами. Можно даже взять весь процесс на себя, реализовав не интерфейс Serializable, а интерфейс Externaiizabie, но тогда придется реали-зовать методы readExternai () и writeExternai о, выполняющие ввод/вывод.

Эти действия выходят за рамки книги. Если вам необходимо полностью освоить процесс сериализации, то обратитесь к спецификации Java Object Serialization Specification, расположенной среди документации J2SDK в каталоге docs\guide\serialization\spec\. Там же есть и примеры программ, реализующих эту спецификацию.


Содержание раздела