Сериализация объектов проводится достаточно разумно и в отношении ссылок, имеющихся в объекте. Сохраняется не только сам образ объекта, но и все связанные с ним объекты, все объекты в связанных объектах, и т. д. Это часто называют «паутиной объектов», к которой можно присоединить одиночный объект, а также массив ссылок на объекты и объекты-члены. Если бы вы создавали свой собственный механизм сериализации, отслеживание всех присутствующих в объектах ссылок стало бы весьма нелегкой задачей. Однако в 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 storage\n"), 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("Память объекта Worm\n"); 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.
Обнаружение класса