Рейтинговые книги
Читем онлайн Философия Java3 - Брюс Эккель

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 109 110 111 112 113 114 115 116 117 ... 132

Судя по всему, при вызове метода 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 =

new ByteArrayOutputStream(); ObjectOutputStream o2 = new 0bject0utputStream(buf2):

02 writeObject(animals).

// Теперь восстанавливаем записанные объекты ObjectlnputStream inl = new ObjectInputStream(

new ByteArrayInputStream(bufl toByteArrayO)). ObjectlnputStream in2 = new ObjectInputStream(

new ByteArrayInputStream(buf2 toByteArrayO));

List

animalsl = (List)inl readObjectO, animals2 = (List)inl readObjectO, ammals3 = (List)in2 readObjectO, printC'animalsl " + animalsl); pnnt("animals2. " + animals2); print("animals3 " + animals3).

}

} /* Output (Sample)

animals- [Bosco the dog[[email protected]]. [email protected] . Ralph the hamster[[email protected]]. [email protected]

, Molly the cat[[email protected]]. [email protected] ]

animalsl [Bosco the dog[[email protected]]. [email protected] . Ralph the hamster[[email protected]]. [email protected]

, Molly the cat[[email protected]], [email protected] ]

animals2 [Bosco the dog[[email protected]], [email protected] , Ralph the hamster[[email protected]]. [email protected]

. Molly the cat[[email protected]], [email protected] ]

animals3 [Bosco the dog[[email protected]], [email protected] , Ralph the hamster[[email protected]], [email protected]

. Molly the cat[[email protected]]. [email protected] ]

*/// ~

В этом примере стоит обратить внимание на использование механизма сериализации и байтового массива для «глубокого копирования» любого объекта с интерфейсом Serializable. (Глубокое копирование — создание дубликата всего графа объектов, а не просто основного объекта и его ссылок.)

Объекты Animal содержат поля типа House. В методе main() создается список ArrayList с несколькими объектами Animal, его дважды записывают в один поток и еще один раз — в отдельный поток. Когда эти списки восстанавливают и распечатывают, получается приведенный ранее результат (объекты при каждом запуске программы будут располагаться в различных областях памяти).

Конечно, нет ничего удивительного в том, что восстановленные объекты и их оригиналы будут иметь разные адреса. Но заметьте тот факт, что адреса в восстановленных объектах animalsl и animals2 совпадают, вплоть до повторения ссылок на объект House, общий для обоих списков. С другой стороны, при восстановлении списка animals3 система не имеет представления о том, что находящиеся в них объекты уже были восстановлены и имеются в программе, поэтому она создает совершенно иное семейство взаимосвязанных объектов.

Если вы будете проводить сериализацию с использованием единого выходного потока, сохраненная сеть объектов гарантированно восстановится в первоначальном виде, без излишних повторений объектов. Конечно, записать объекты можно тогда, когда они еще не приняли окончательного состояния, но это уже на вашей совести — сохраненные объекты останутся в том состоянии, в котором вы их записали (с теми связями, что у них были на момент сериализации).

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

Следующий пример — имитатор воображаемой системы автоматизированного проектирования (CAD), в котором используется такой подход. Вдобавок в нем продемонстрировано сохранение статических (static) нолей — если вы взглянете на документацию JDK, то увидите, что класс Class реализует интерфейс Serializable, поэтому для сохранения статических данных достаточно сохранить объект Class. Это достаточно разумное решение.

//• i o/StoreCADState.java

// Сохранение состояния вымышленной системы CAD

import java io *;

import java.util.*:

abstract class Shape implements Serializable {

public static final int RED = 1, BLUE = 2. GREEN = 3: private int xPos, yPos. dimension, private static Random rand = new Random(47), private static int counter = 0; public abstract void setColor(int newColor); public abstract int getColorO; public Shape(int xVal. int yVal, int dim) { xPos = xVal; yPos = yVal; dimension = dim;

}

public String toStringO { return getClassO +

"color[" + getColorO + "] xPos[" + xPos + "] yPos[" + yPos + "] dim[" + dimension + "]n";

}

public static Shape randomFactory() { int xVal = rand.nextlnt(lOO); int yVal = rand.nextlnt(lOO); int dim = rand.nextlnt(lOO); switch(counter++ % 3) { default:

case 0: return new Circle(xVal. yVal. dim); case 1: return new Square(xVal. yVal, dim); case 2: return new LineCxVal. yVal. dim);

}

class Circle extends Shape {

private static int color = RED;

public CircleCint xVal. int yVal, int dim) {

super(xVal, yVal, dim).

public void setColor(int newColor) { color = newColor. } public int getColorO { return color. }

}

class Square extends Shape { private static int color, public Squared nt xVal. int yVal. int dim) { super(xVal. yVal. dim). color = RED.

}

public void setColor(int newColor) { color = newColor. } public int getColorO { return color. }

}

class Line extends Shape {

1 ... 109 110 111 112 113 114 115 116 117 ... 132
На этой странице вы можете бесплатно читать книгу Философия Java3 - Брюс Эккель бесплатно.
Похожие на Философия Java3 - Брюс Эккель книги

Оставить комментарий