Брюс Эккель - Философия Java3
Сериализация объектов также необходима визуальным компонентам Java-Bean. Информация о состоянии визуальных компонентов обычно изменяется во время разработки. Эту информацию о состоянии необходимо сохранить, а затем, при запуске программы, восстановить; данную задачу решает сериализация объектов.
Сериализовать объект достаточно просто, если он реализует интерфейс Seri-alizable (это интерфейс для самоидентификации, в нем нет ни одного метода). Когда в язык был добавлен механизм сериализации, во многие классы стандартной библиотеки внесли изменения так, чтобы они были готовы к сериализации. К таким классам относятся все классы-оболочки для простейших типов, все классы контейнеров и многие другие. Даже объекты Class, представляющие классы, можно сериализовать.
Чтобы сериализовать объект, требуется создать выходной поток Output-Stream, который нужно вложить в объект ObjectOutputStream. По сути, вызов метода writeObject() осуществляет сериализацию объекта, и далее вы пересылаете его в выходной поток данных OutputStream. Для восстановления объекта необходимо надстроить объект ObjectlnputStream для входного потока InputStream, а затем вызвать метод readObject(). Как обычно, такой метод возвращает ссылку на обобщенный объект Object, поэтому после вызова метода следует провести нисходящее преобразование для получения объекта нужного типа.
Сериализация объектов проводится достаточно разумно и в отношении ссылок, имеющихся в объекте. Сохраняется не только сам образ объекта, но и все связанные с ним объекты, все объекты в связанных объектах, и т. д. Это часто называют «паутиной объектов», к которой можно присоединить одиночный объект, а также массив ссылок на объекты и объекты-члены. Если бы вы создавали свой собственный механизм сериализации, отслеживание всех присутствующих в объектах ссылок стало бы весьма нелегкой задачей. Однако в Java никаких трудностей со ссылками нет — судя по всему, в этот язык встроен достаточно эффективный алгоритм создания графов объектов. Следующий пример проверяет механизм сериализации: мы создаем цепочку связанных объектов, каждый из которых связан со следующим сегментом цепочки, а также имеет массив ссылок на объекты другого класса с именем Data:
// io/Worm java
// Тест сериализации объектов
import java.io.*;
import java util *;
import static net mindview util.Print.*;
class Data implements Serializable { private int n;
public Data(int n) { this n = n, }
public String toStringO { return Integer toString(n); }
}
public class Worm implements Serializable {
private static Random rand = new Random(47); private Data[] d = {
new Data(rand.nextlnt(10)). new Data(rand nextlnt(lO)), new Data(rand nextlnt(lO))
}.
private Worm next.
private char с. продолжение &
// значение i == количество сегментов public Worm(int i, char x) {
print("Конструктор Worm- " + i); с = x, if(--i > 0)
next = new Worm(i, (char)(x +1)),
}
public WormO {
print("Конструктор по умолчанию"),
}
public String toStringO {
StringBuilder result = new StringBuilderC•"); result.append(c), result.append("("); for(Data dat : d)
result.append(dat); result. appendC')"); if(next != null)
result.append(next), return result toStringO,
}
public static void main(String[] args) throws ClassNotFoundException, IOException { Worm w = new Worm(6, 'a'); printCw = " + w);
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("worm.out")); out writeObjectCWorm storagen"), out.writeObject(w);
out closeO; // Также очистка буфера вывода Object InputStream in = new ObjectlnputStrearrK
new FileInputStream("worm.out")), String s = (String)in.readObjectO, Worm w2 = (Worm)in.readObjectO. print(s + • "w2 = " + w2); ByteArrayOutputStream bout =
new ByteArrayOutputStream(); ObjectOutputStream out2 = new ObjectOutputStream(bout); out2.writeObject("Память объекта Wormn"); out2.write0bject(w); out2.flush();
ObjectInputStream in2 = new ObjectInputStream( new ByteArrayInputStream(bout toByteArrayO)); s = (String)in2 readObject(); Worm w3 = (Worm)in2.read0bject(); print(s + "w3 = " + w3);
}
} /* Output: Конструктор Worm- 6 Конструктор Worm. 5 Конструктор Worm- 4 Конструктор Worm. 3 Конструктор Worm: 2 Конструктор Worm: 1
w = :a(853) b(119).c(802) d(788) e(199):f(881) Память объекта Worm
w2 = .a(853):b(119)-c(802)-d(788)-e(199)-f(881) Память объекта Worm
w3 - :а(853):b(119):с(802):d(788):е(199):f(881)
Чтобы пример был интереснее, массив объектов Data в классе Worm инициализируется случайными числами. (Таким образом, нельзя заподозрить компилятор в том, что он использует дополнительную информацию для хранения объектов.) Каждый объект Worm помечается порядковым номером-символом (char), который автоматически генерируется в процессе рекурсивного формирования связанной цепочки объектов Worm. При создании цепочки ее размер указывается в конструкторе класса Worm. Для инициализации ссылки next рекурсивно вызывается конструктор класса Worm, однако с каждым разом размер цепочки уменьшается на единицу. В последнем сегменте цепочки ссылка next остается со значением null, что указывает на конец цепочки.
Все это делалось лишь по одной причине: для создания более или менее сложной структуры, которая не может быть сериализована тривиальным образом. Впрочем, сам акт сериализации проходит проще простого. После создания потока ObjectOutputStream (на основе другого выходного потока), метод write-Object() записывает в него объект. Заметьте, что в поток также записывается строка (String). В этот же поток можно поместить все примитивные типы, используя те же методы, что и в классе DataOutputStream (оба потока реализуют одинаковый интерфейс).
В программе есть два похожих фрагмента кода. В первом запись и чтение производится в файл, а во втором для разнообразия хранилищем служит массив байтов ByteArray. Чтение и запись объектов посредством сериализации возможна в любые потоки, в том числе и в сетевые соединения.
Из выходных данных видно, что восстановленный объект в самом деле содержит все ссылки, которые были в исходном объекте.
Заметьте, что в процессе восстановления объекта, реализующего интерфейс Serializable, никакие конструкторы (даже конструктор по умолчанию) не вызываются. Объект восстанавливается целиком и полностью из данных, считанных из входного потока InputStream.
Обнаружение класса
Вам может быть интересно, что необходимо для восстановления объекта после проведения сериализации. Например, предположим, что вы создали объект, се-риализовали его и отправили в виде файла или через сетевое соединение на другой компьютер. Сумеет ли программа на другом компьютере реконструировать объект, опираясь только на те данные, которые были записаны в файл в процессе сериализации?
Самый надежный способ получить ответ на этот вопрос — провести эксперимент. Следующий файл располагается в подкаталоге данной главы:
//: io/Alien java
// Сериализуемый класс
import java.io.*;
public class Alien implements Serializable {} ///:-
Файл с программой, создающей и сериализующей объект Alien, находится в том же подкаталоге:
// io/FreezeAlien java
// Создание файла с данными сериализации
import java io *,
public class FreezeAlien {
public static void main(Stnng[] args) throws Exception { ObjectOutput out = new ObjectOutputStream( new FileOutputStreamC'X file")). Alien quellek = new AlienO. out writeObject(quellek),
}
} III ~
Вместо того чтобы перехватывать и обрабатывать исключения, программа идет по простому пути — исключения передаются за пределы main(), поэтому сообщения о них будут выдаваться на консоль.
После того как вы скомпилируете и запустите этот код, в каталоге с12 появится файл с именем X.file. Следующая программа скрыта от чужих глаз в «секретном» подкаталоге xfiles:
//• io/xfi1es/ThawAlien java
// Попытка восстановления сериализованного файла
// без сохранения класса объекта в зтом файле.
// {RunByHand}
import java io *,
public class ThawAlien {
public static void main(Strmg[] args) throws Exception { ObjectInputStream in = new ObjectInputStream(
new FileInputStream(new FileC. ". "X file"))). Object mystery = in readObjectO, System out println(mystery getClassO),
}
} /* Output
class Alien */// -
Даже открыв файл и прочитав из него данные для восстановления объекта mystery, виртуальная машина Java QVM) не сможет найти файл Alien.class; объект Class для объекта Alien будет в не досягаемости (в примере сознательно не рассматривается возможность обнаружения через переменную окружения CLASSPATH). Возникнет исключение ClassNotFoundException.
Управление сериализацией
Как вы могли убедиться, стандартный механизм сериализации достаточно прост в применении. Но что, если у вас возникли особые требования? Возможно, из соображений безопасности вы не хотите сохранять некоторые части вашего объекта, или сериализовать какой-либо объект, содержащийся в главном объекте, не имеет смысла, так как немного погодя его все равно потребуется создать заново.
Вы можете управлять процессом сериализации, реализуя в своем классе интерфейс Externalizable вместо интерфейса Serializable. Этот интерфейс расширяет оригинальный интерфейс Serializable и добавляет в него два метода, writeExternal() и readExternal(), которые автоматически вызываются в процессе сериализации и восстановления объектов, позволяя вам попутно выполнить специфические действия для конкретного объекта.
В следующем примере продемонстрирована простая реализация интерфейса Externalizable. Заметьте также, что классы Blipl и Blip2 практически одинаковы, если не считать одного малозаметного отличия:
// io/Blips.java
// Простая реализация интерфейса Externalizable. . с проблемами
import java io *;
import static net mindview util Print *;
class Blipl implements Externalizable {
public BliplO {
print("Конструктор Blipl"),
}
public void writeExternal(ObjectOutput out) throws IOException { print("Blipl writeExternal"),
}
public void readExternal(Objectlnput in)