Брюс Эккель - Философия Java3
}
public void readExternal(Objectlnput in)
throws IOException, ClassNotFoundException { print("Blipl readExternal");
class Blip2 implements Externalizable { Blip2() {
print("Конструктор Blip2");
}
public void writeExternal(ObjectOutput out) throws IOException { print("Blip2.writeExternal"),
}
public void readExternal(Objectlnput in)
throws IOException, ClassNotFoundException { print("Blip2.readExternal"),
public class Blips {
public static void main(String[] args) throws IOException, ClassNotFoundException { print("Создание объектов:"); Blipl bl = new BliplO; Blip2 b2 = new Blip20,
ObjectOutputStream о = new ObjectOutputStream(
new FileOutputStream("Blips out")); print("Сохранение объектов-"); o.writeObject(bl); о write0bject(b2); о closeO;
// Восстановление сохраненных объектов- продолжение &
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("Blips out")); print("Восстановление bl:"); bl = (Bli pi)i n.readObject(); // Вот те раз! Исключение //! print("Восстановление Ь2:");
//! Ь2 = (Blip2)in readObjectО; }
} /* Output: Создание объектов-Конструктор Blipl Конструктор Blip2 Сохранение объектов: Blipl writeExternal Blip2 writeExternal Восстановление bl: Конструктор Blipl Blipl readExternal *///:-
Итак, объект BLip2 в программе не восстанавливается — попытка приводит к возникновению исключения. Заметили ли вы различие между классами Blipl и ВИр2? Конструктор класса Blipl объявлен открытым (public), в то время как конструктор класса Blip2 таковым не является, и именно это приводит к исключению в процессе восстановления. Попробуйте объявить конструктор класса Blip2 открытым и удалить комментарии //!, и вы увидите, что все работает, как и было запланировано.
При восстановлении объекта Ы вызывается конструктор по умолчанию класса Blipl. Это отличается от восстановления объекта, реализующего интерфейс Serializable, которое проводится на основе данных сериализации, без вызова конструкторов. В случае с объектом Externalizable происходит нормальный процесс конструирования (включая инициализацию в точке определения), и далее вызывается метод readExternal(). Вам следует иметь это в виду при реализации объектов Externalizable — в особенности обратите внимание на то, что вызывается конструктор по умолчанию.
Следующий пример показывает, что надо сделать для полноты операций сохранения и восстановления объекта Externalizable:
// io/Blip3 java
// Восстановление объекта Externalizable import java io *,
import static net.mindview util.Print *;
public class Blip3 implements Externalizable { private int i.
private String s; // Без инициализации public Blip3() {
print("Конструктор Blip3"). // s, i не инициализируются
}
public Blip3(String x. int a) {
print("Blip3(String x, int a)"), s = x, i = a.
// s и i инициализируются только вне конструктора по умолчанию
public String toStringO { return s + i; } public void writeExternal(ObjectOutput out) throws IOException {
print("Blip3.writeExternal"); // Необходимо действовать так: out.writeObject(s); out.writelntCi);
}
public void readExternal(Objectlnput in) throws IOException, ClassNotFoundException { pri nt("B1i p3.readExternal"); // Необходимо действовать так: s = (String)in.readObjectO; i = in.readlntO:
}
public static void main(String[] args) throws IOException, ClassNotFoundException { print("Создание объектов:"): Blip3 ЬЗ = new Blip3("Строка ", 47);
print(b3);
ObjectOutputStream о = new ObjectOutputStream(
new File0utputStream("Blip3 out")); print("Сохранение объекта:"); o.write0bject(b3); о.closeO: // Now get it back:
ObjectInputStream in = new ObjectInputStream(
new Fi1eInputStream("B1ip3.out")); print("Восстановление ЬЗ:"); ЬЗ = (Blip3)in. readObjectO; print(b3);
}
} /* Output: Создание объектов: Blip3(String x, int a) Строка 47
Сохранение объекта: Blip3.writeExternal Восстановление ЬЗ: Конструктор Blip3 B1i рЗ.readExternal Строка 47 *///:-
Поля s и i инициализируются только во втором конструкторе, но не в конструкторе по умолчанию. Это значит, что, если переменные s и i не будут инициализированы в методе readExternal(), s останется ссылкой null, a i будет равно нулю (так как при создании объекта его память обнуляется). Если вы закомментируете две строки после фраз Необходимо действовать так и запустите программу, то обнаружите, что так оно и будет: в восстановленном объекте ссылка s имеет значение null, а целое i равно нулю.
Если вы наследуете от объекта, реализующего интерфейс Externalizable, то при выполнении сериализации следует вызывать методы базового класса writeExternal() и readExternal(), чтобы правильно сохранить и восстановить свой объект.
Итак, чтобы сериализация выполнялась правильно, нужно не просто записать всю значимую информацию в методе writeExternal() (для объектов Externalizable не существует автоматической записи объектов-членов), но и восстановить ее затем в методе readExternal(). Хотя сначала можно запутаться и подумать, что из-за вызова конструктора по умолчанию все необходимые действия по записи и восстановлению объектов Externalizable происходят сами по себе. Но это не так.
Ключевое слово transient
При управлении процессом сериализации может возникнуть ситуация, при которой автоматическое сохранение и восстановление некоторого подобъекта нежелательно — например, если в объекте хранится некоторая конфиденциальная информация (пароль и т. д.). Даже если информация в объекте описана как закрытая (private), это не спасает ее от сериализации, после которой можно извлечь секретные данные из файла или из перехваченного сетевого пакета.
Первый способ предотвратить сериализацию некоторой важной части объекта — реализовать интерфейс Externalizable, что уже было показано. Тогда автоматически вообще ничего не записывается, и управление отдельными элементами находится в ваших руках (внутри writeExternal).
Однако работать с объектами Serializable удобнее, поскольку сериализация для них проходит полностью автоматически. Чтобы запретить запись некоторых полей объекта Serializable, воспользуйтесь ключевым словом transient. Фактически оно означает: «Это не нужно ни сохранять, ни восстанавливать — это мое дело».
Для примера возьмем объект Login, который содержит информацию о некотором сеансе входа в систему, с вводом пароля и имени пользователя. Предположим, что после проверки информации ее необходимо сохранить, выборочно, без пароля. Проще всего удовлетворить заявленным требованиям, реализуя интерфейс Serializable и объявляя поле с паролем password как transient. Вот как это будет выглядеть:
//• io/Logon java
// Ключевое слово "transient"
import java util concurrent *.
import java.io.*;
import java util *.
import static net mindview util Print.*,
public class Logon implements Serializable { private Date date = new DateO. private String username; private transient String password: public Logon(String name, String pwd) { username = name, password = pwd.
}
public String toStringO {
return "информация о сеансе. n пользователь. " + username +
"п дата " + date + "п пароль " + password;
public static void main(String[] args) throws Exception { Logon a = new Logon("Пользователь". "Пароль"), print("Сеанс a = " + a). ObjectOutputStream о = new ObjectOutputStream(
new FileOutputStream("Logon out")), о writeObject(a); о closeO,
TimeUnit.SECONDS.sleep(l). // Задержка // Восстановление-
ObjectlnputStream in = new ObjectInputStream(
new FileInputStream("Logon out")), print ("Восстановление объекта Время: " + new DateO); a = (Logon)in readObjectO, print("Сеанс a = " + a).
}
} /* Output
Сеанс a = информация о сеансе пользователь- Пользователь дата- Sat Nov 19 15-03 26 MST 2005 пароль Пароль
Восстановление объекта. Время: Sat Nov 19 15.03 28 MST 2005
Сеанс а = информация о сеансе пользователь. Пользователь дата Sat Nov 19 15.03 26 MST 2005 пароль, null
*/// ~
Поля date и username не имеют модификатора transient, поэтому сериализация для них проводится автоматически. Однако поле password описано как transient и поэтому не сохраняется на диске; механизм сериализации его также игнорирует. При восстановлении объекта поле password равно null. Заметьте, что при соединении строки (String) со ссылкой null перегруженным оператором + ссылка null автоматически преобразуется в строку null.
Также видно, что поле date сохраняется на диске и при восстановлении его значение не меняется.
Так как объекты Externalizable по умолчанию не сохраняют полей, ключевое слово transient для них не имеет смысла. Оно применяется только для объектов Serializable.
Альтернатива для Externalizable
Если вас по каким-либо причинам не прельщает реализация интерфейса Externalizable, существует и другой подход. Вы можете реализовать интерфейс Serializable и добавить (заметьте, что я сказал «добавить», а не «переопределить» или «реализовать») методы с именами writeObject() и readObject(). Они автоматически вызываются при сериализации и восстановлении объектов. Иначе говоря, эти два метода заменят собой сериализацию по умолчанию.
Эти методы должны иметь жестко фиксированную сигнатуру:
private void writeObject(ObjectOutputStream stream)
throws IOException;
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException
С точки зрения проектирования программы этот подход вообще непонятен. Во-первых, можно подумать, что, раз уж эти методы не являются частью базового класса и не определены в интерфейсе Serializable, у них должен быть свой собственный интерфейс. Но так как методы объявлены закрытыми (private), вызываться они могут лишь членами их собственного класса. Однако члены обычных классов их не вызывают, вместо этого вызов исходит из методов writeObject() и readObject() классов ObjectlnputStream и ObjectOutputStream. (Я не стану разражаться долгой тирадой о выборе тех же имен методов, а скажу лишь одно слово: неразумно.) Интересно, каким образом классы ObjectlnputStream и ObjectOutputStream обращаются к закрытым членам другого класса? Можно лишь предположить, что это относится к таинству сериализации.
Так или иначе, все методы, описанные в интерфейсе, реализуются как открытые (public), поэтому, если методы writeObject() и readObject() должны быть закрытыми (private), они не могут быть частью какого-либо интерфейса. Однако вам необходимо строго следовать указанной нотации, и результат очень похож на реализацию интерфейса.