Философия Java3
Шрифт:
Эти методы должны иметь жестко фиксированную сигнатуру:
private void writeObject(ObjectOutputStream stream)
throws IOException;
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException
С точки зрения проектирования программы этот подход вообще непонятен. Во-первых, можно подумать, что, раз уж эти методы не являются частью базового класса и не определены в интерфейсе Serializable, у них должен быть свой собственный интерфейс. Но так как методы объявлены закрытыми (private), вызываться они могут лишь членами их собственного класса. Однако члены обычных классов их не вызывают, вместо этого вызов
Так или иначе, все методы, описанные в интерфейсе, реализуются как открытые (public), поэтому, если методы writeObject и readObject должны быть закрытыми (private), они не могут быть частью какого-либо интерфейса. Однако вам необходимо строго следовать указанной нотации, и результат очень похож на реализацию интерфейса.
Судя по всему, при вызове метода ObjectOutputStream.writeObject передаваемый ему объект Serializable тщательно анализируется (вне всяких сомнений, с использованием механизма рефлексии) в поисках его собственного метода writeObject. Если такой метод существует, процесс стандартной сериализации пропускается, и вызывается метод объекта writeObject. Аналогичные действия происходят и при восстановлении объекта.
Существует и еще одна хитрость. В вашем собственном методе writeObject можно вызвать используемый в обычной сериализации метод writeObject, для этого вызывается метод defaultWriteObject. Аналогично, в методе readObject можно вызвать метод стандартного восстановления defaultReadObject. Следующий пример показывает, как производится пользовательское управление хранением и восстановлением объектов Serializable:
//: io/SerialCtl java
// Управление сериализацией с определением собственных // методов writeObjectO и readObjectO. import java.io.*;
public class SerialCtl implements Serializable { private String a; private transient String b: public SerialCtl(String aa, String bb) {
a = "He объявлено transient: " + aa: b = "Объявлено transient: " + bb:
}
public String toStringO { return a + "\n" + b: } private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject: stream.writeObject(b);
}
private void readObject(ObjectInputStream stream)
throws IOException. ClassNotFoundException { stream.defaultReadObject, b = (String)stream.readObjectO.
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
SerialCtl sc = new SerialCtl("Testl". "Test2"); System out.printin("Перед записью \n" + sc); ByteArrayOutputStream buf= new ByteArrayOutputStream; ObjectOutputStream о = new ObjectOutputStream(buf); о writeObject(sc). // Now get it back.
ObjectlnputStream in = new ObjectInputStream(
new ByteArraylnputStream(buf.toByteArrayO)); SerialCtl sc2 = (SerialCtl)in readObject; System out println("После восстановления:\n" + sc2),
}
} /* Output: Перед записью
He объявлено transient- Testl Объявлено transient: Test2 После восстановления-He объявлено transient: Testl Объявлено transient: Test2 *///.-
В данном примере одно из строковых полей класса объявлено с ключевым словом transient, чтобы продемонстрировать, что
такие поля при вызове метода defaultWriteObject не сохраняются. Строка сохраняется и восстанавливается программой явно. Поля класса инициализируются в конструкторе, а не в точке определения; это демонстрирует, что они не инициализируются каким-либо автоматическим механизмом в процессе восстановления.Если вы собираетесь использовать встроенный механизм сериализации для записи обычных (He– transient) составляющих объекта, нужно при записи объекта в первую очередь вызвать метод defaultWriteObject, а при восстановлении объекта — метод defaultReadObject. Это вообще загадочные методы. Например, если вызвать метод defaultWriteObject для потока ObjectOutputStream без передачи аргументов, он все же как-то узнает, какой объект надо записать, где находится ссылка на него и как записать все его He-transient составляющие. Мистика.
Сохранение и восстановление transient-объектов выполняется относительно просто. В методе main создается объект SerialCtl, который затем сериализуется потоком ObjectOutputStream. (При этом для вывода используется буфер, а не файл — для потока ObjectOutputStream это несущественно.) Непосредственно сериализация выполняется в строке
о writeObject(sc);
Метод writeObject должен определить, имеется ли в объекте sc свой собственный метод writeObject. (При этом проверка на наличие интерфейса или класса не проводится — их нет — поиск метода проводится с помощью рефлексии.) Если поиск успешен, найденный метод вовлекается в процедуру сериализации. Примерно такой же подход наблюдается и при восстановлении объекта методом readObject. Возможно, это единственное решение задачи, но все равно выглядит очень странно.
Долговременное хранение
Было бы замечательно привлечь технологию сериализации, чтобы сохранить состояние вашей программы для его последующего восстановления. Но перед тем как это делать, необходимо ответить на несколько вопросов. Что произойдет при сохранении двух объектов, содержащих ссылку на некоторый общий третий объект? Когда вы восстановите эти объекты, сколько экземпляров третьего объекта появится в программе? А если вы сохраните объекты в отдельных файлах, а затем десериализуете их в разных частях программы? Следующий пример демонстрирует возможные проблемы:
// io/MyWorld java import java io *. import java util *.
import static net mindview util Print *.
class House implements Serializable {}
class Animal implements Serializable { private String name, private House preferredHouse; Animal(String nm, House h) { name = nm, preferredHouse = h;
}
public String toStringO {
return name + "[" + super.toString + "], " + preferredHouse + "\n",
public class MyWorld {
public static void main(String[] args) throws IOException, ClassNotFoundException { House house = new HouseO: List<Animal> animals = new ArrayList<Animal>, animals add(new Animal("Bosco the dog", house)): animals.add(new Animal("Ralph the hamster", house)), animals.add(new Animal("Molly the cat", house)): printC'animals " + animals), ByteArrayOutputStream bufl =
new ByteArrayOutputStream. ObjectOutputStream ol = new ObjectOutputStream(bufl). ol.writeObject(animals):
01 writeObject(animals). // Записываем второй набор // Запись в другой поток ByteArrayOutputStream buf2 =