Программирование на Java
Шрифт:
Выполнение этой программы приведет к выводу на экран примерно следующего:
Constructing line: 1
Constructing line: 2
line 1 = Line@7d39
line 2 = Line@4ec
Read objects:
Line: 1
Object reference: Line@331e
from point (1.0,1.0) reference=Point@36bb
to point (2.0,2.0) reference=Point@386e
Line: 2
Object reference: Line@6706
from point (2.0,2.0) reference=Point@386e
to point (3.0,3.0) reference=Point@68ae
Line: 1
Object reference: Line@331e
from point (1.0,1.0) reference=Point@36bb
to point (2.0,2.0) reference=Point@386e
Из примера видно, что после восстановления у линий сохраняется общая точка, описываемая одним и тем же объектом (хеш-код 386e ).
Третий записанный объект идентичен первому, причем, совпадают даже объектные ссылки. Несмотря на то, что при записи третьего объекта значение index было изменено на 3, в десериализованном объекте оно осталось равным 1. Так произошло потому, что объект, описывающий первую линию, уже был задействован в сериализации и, встретившись во второй раз, повторно записан не был.
Чтобы указать, что сеанс сериализации завершен, и получить возможность передавать измененные объекты, у ObjectOutputStream нужно вызвать метод reset. В рассматриваемом примере для этого достаточно убрать комментарий в строке
//oos.reset;
Если теперь запустить программу, то можно увидеть, что третий объект получит номер 3.
Constructing line: 1
Constructing line: 2
line 1 = Line@ea2dfe
line 2 = Line@7182c1
Read objects:
Line: 1
Object reference: Line@a981ca
from point (1.0,1.0) reference=Point@1503a3
to point (2.0,2.0) reference=Point@a1c887
Line: 2
Object reference: Line@743399
from point (2.0,2.0) reference=Point@a1c887
to point (3.0,3.0) reference=Point@e7b241
Line: 3
Object reference: Line@67d940
from point (1.0,1.0) reference=Point@e83912
to point (2.0,2.0) reference=Point@fae3c6
Пример 15.14.
Однако это будет уже новый объект, ссылка на который отличается от первой считанной линии. Более того, обе точки будут также описываться новыми объектами. То есть в новом сеансе все объекты были записаны, а затем восстановлены заново.
Расширение стандартной сериализации
Некоторым сложно организованным классам требуется особый подход для сериализации. Для расширения стандартного механизма можно объявить в классе два метода с точно такой сигнатурой:
private void writeObject(
java.io.ObjectOutputStream out)
throws IOException;
private void readObject(
java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
Если в классе объявлены такие методы, то при сериализации объекта для записи его состояния будет вызван writeObject, который должен сгенерировать последовательность байт и записать ее в поток out, полученный в качестве аргумента. При этом можно вызвать стандартный механизм записи объекта путем вызова метода
out.defaultWriteObject;
Этот метод запишет все не- transient и не- static поля в поток данных.
В
in.defaultReadObject;
Этот метод считывает описание объекта из потока и присваивает значения соответствующих полей в текущем объекте.
Если же процедура сериализации в корне отличается от стандартной, то для таких классов предназначен альтернативный интерфейс java.io.Externalizable.
При использовании этого интерфейса в поток автоматически записывается только идентификация класса. Сохранить и восстановить всю информацию о состоянии экземпляра должен сам класс. Для этого в нем должны быть объявлены методы writeExternal и readExternal интерфейса Externalizable. Эти методы должны обеспечить сохранение состояния, описываемого полями самого класса и его суперкласса.
При восстановлении Externalizable -объекта экземпляр создается путем вызова конструктора без аргументов, после чего вызывается метод readExternal.
Метод writeExternal имеет сигнатуру:
void writeExternal(ObjectOutput out)
throws IOException;
Для сохранения состояния вызываются методы ObjectOutput, с помощью которых можно записать как примитивные, так и объектные значения. Для корректной работы в соответствующем методе
void readExternal(ObjectInput in)
throws IOException,ClassNotFoundException;
эти значения должны быть считаны в том же самом порядке.
Классы Reader и Writer и их наследники
Рассмотренные классы – наследники InputStream и OutputStream – работают с байтовыми данными. Если с их помощью записывать или считывать текст, то сначала необходимо сопоставить каждому символу его числовой код. Такое соответствие называется кодировкой.
Известно, что Java использует кодировку Unicode, в которой символы представляются двухбайтовым кодом. Байтовые потоки зачастую работают с текстом упрощенно – они просто отбрасывают старший байт каждого символа. В реальных же приложениях могут использовать различные кодировки (даже для русского языка их существует несколько). Поэтому в версии Java 1.1 появился дополнительный набор классов, основывающийся на типах Reader и Writer. Их иерархия представлена на рис. 15.2.
Эта иерархия очень схожа с аналогичной для байтовых потоков InputStream и OutputStream. Главное отличие между ними – Reader и Writer работают с потоком символов ( char ). Только чтение массива символов в Reader описывается методом read(char[]), а запись в Writer – write(char[]).
В таблице 15.1 приведены соответствия классов для байтовых и символьных потоков.
Рис. 15.2. Иерархия классов Reader и Writer.