Как вы могли убедиться, стандартный механизм сериализации достаточно прост в применении. Но что, если у вас возникли особые требования? Возможно, из соображений безопасности вы не хотите сохранять некоторые части вашего объекта, или сериализовать какой-либо объект, содержащийся в главном объекте, не имеет смысла, так как немного погодя его все равно потребуется создать заново. Вы можете управлять процессом сериализации, реализуя в своем классе интерфейс Externalizable вместо интерфейса Serializable. Этот интерфейс расширяет оригинальный интерфейс Serializable и добавляет в него два метода, writeExternal() и readExternal(), которые автоматически вызываются в процессе сериализации и восстановления объектов, позволяя вам попутно выполнить специфические действия для конкретного объекта.
В следующем примере продемонстрирована простая реализация интерфейса Externalizable. Заметьте также, что классы Blip1 и Blip2 практически одинаковы, если не считать одного малозаметного отличия:
//: io/Blips.java
// Простая реализация интерфейса Externalizable... с проблемами.
import java.io.*;
import static net.mindview.util.Print.*;
class Blip1 implements Externalizable {
public Blip1() {
print("Blip1 Constructor");
}
public void writeExternal(ObjectOutput out)
throws IOException {
print("Blip1.writeExternal");
}
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
print("Blip1.readExternal");
}
}
class Blip2 implements Externalizable {
Blip2() {
print("Blip2 Constructor");
}
public void writeExternal(ObjectOutput out)
throws IOException {
print("Blip2.writeExternal");
}
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
print("Blip2.readExternal");
}
}
public class Blips {
public static void main(String[] args)
throws IOException, ClassNotFoundException {
print("Constructing objects:");
Blip1 b1 = new Blip1();
Blip2 b2 = new Blip2();
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("Blips.out"));
print("Saving objects:");
o.writeObject(b1);
o.writeObject(b2);
o.close();
// Now get them back:
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("Blips.out"));
print("Recovering b1:");
b1 = (Blip1)in.readObject();
// OOPS! Throws an exception:
//! print("Recovering b2:");
//! b2 = (Blip2)in.readObject();
}
}
<spoiler text="Output:">
Constructing objects:
Blip1 Constructor
Blip2 Constructor
Saving objects:
Blip1.writeExternal
Blip2.writeExternal
Recovering b1:
Blip1 Constructor
Blip1.readExternal
</spoiler> Итак, объект BLip2 в программе не восстанавливается — попытка приводит к возникновению исключения. Заметили ли вы различие между классами Blip1 и Вlip2? Конструктор класса Blip1 объявлен открытым (public), в то время как конструктор класса Blip2таковым не является, и именно это приводит к исключению в процессе восстановления. Попробуйте объявить конструктор класса Blip2 открытым и удалить комментарии //!, и вы увидите, что все работает, как и было запланировано. При восстановлении объекта b1вызывается конструктор по умолчанию класса Blip1. Это отличается от восстановления объекта, реализующего интерфейс 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; // No initialization
public Blip3() {
print("Blip3 Constructor");
// s, i not initialized
}
public Blip3(String x, int a) {
print("Blip3(String x, int a)");
s = x;
i = a;
// s & i initialized only in non-default constructor.
}
public String toString() { return s + i; }
public void writeExternal(ObjectOutput out)
throws IOException {
print("Blip3.writeExternal");
// You must do this:
out.writeObject(s);
out.writeInt(i);
}
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
print("Blip3.readExternal");
// You must do this:
s = (String)in.readObject();
i = in.readInt();
}
public static void main(String[] args)
throws IOException, ClassNotFoundException {
print("Constructing objects:");
Blip3 b3 = new Blip3("A String ", 47);
print(b3);
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("Blip3.out"));
print("Saving object:");
o.writeObject(b3);
o.close();
// Now get it back:
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("Blip3.out"));
print("Recovering b3:");
b3 = (Blip3)in.readObject();
print(b3);
}
}
<spoiler text="Output:">
Constructing objects:
Blip3(String x, int a)
A String 47
Saving object:
Blip3.writeExternal
Recovering b3:
Blip3 Constructor
Blip3.readExternal
A String 47
</spoiler> Поля s и і инициализируются только во втором конструкторе, но не в конструкторе по умолчанию. Это значит, что, если переменные s и і не будут инициализированы в методе readExternal(), s останется ссылкой null, а і будет равно нулю (так как при создании объекта его память обнуляется). Если вы закомментируете две строки после фраз Необходимо действовать так и запустите программу, то обнаружите, что так оно и будет: в восстановленном объекте ссылка s имеет значение null, а целое і равно нулю. Если вы наследуете от объекта, реализующего интерфейс Externalizable, то при выполнении сериализации следует вызывать методы базового класса writeExternal() и readExternal(), чтобы правильно сохранить и восстановить свой объект. Итак, чтобы сериализация выполнялась правильно, нужно не просто записать всю значимую информацию в методе writeExternal() (для объектов Externalizable не существует автоматической записи объектов-членов), но и восстановить ее затем в методе readExternal(). Хотя сначала можно запутаться и подумать, что из-за вызова конструктора по умолчанию все необходимые действия по записи и восстановлению объектов Externalizable происходят сами по себе. Но это не так.