Философия Java

         

Альтернатива Externalizable


Если вы не достаточно сильны в реализации интерфейса Externalizable, существует другой подход. Вы можете реализовать интерфейс Serializable и добавить (обратите внимание, я сказал “добавить”, а не “перекрыть” или “реализовать”) методы, называемые writeObject( ) и readObject( ), которые будут автоматически вызваны, когда объект будет, соответственно, сериализоваться и десериализоваться. То есть, если вы обеспечите эти два метода, они будут использоваться взамен сериализации по умолчанию.

Методы должны иметь следующие точные сигнатуры:

private void writeObject(ObjectOutputStream stream) throws IOException;

private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException

С точки зрения дизайна, это мистические вещи. Прежде всего, вы можете подумать так, потому что эти методы не являются частью базового класса или интерфейса Serializable, следовательно, они не будут определены в своем собственном интерфейсе. Но обратите внимание, что они объявлены как private, что означает, что они будут вызываться только другим членом этого класса. Однако на самом деле вы не вызываете их из других членов этого класса, а вместо этого методы writeObject( ) и readObject( ), принадлежащие объекту ObjectOutputStream и ObjectInputStream, вызывают методы writeObject( ) и readObject( ) вашего объекта. (Обратите внимание на мою невероятную сдержанность, из-за которой я не пускаюсь в пространные обличительные речи по поводу использования одних и тех же имен методов здесь. Я просто скажу: путаница.) Вы можете быть удивлены, как объекты ObjectOutputStream и ObjectInputStream получают доступ к private методам вашего класса. Мы можем только иметь в виду, что эта часть составляет магию сериализации.

В любом случае, все, что определено в интерфейсе, автоматически становится public, поэтому, если writeObject( ) и readObject( ) должны быть private, то они не могут быть частью интерфейса. Так как вы должны следовать точным сигнатурам, получаемый эффект тот же самые, как если бы вы реализовали interface.


Может показаться, что когда вы вызываете ObjectOutputStream.writeObject( ), объект с интерфейсом Serializable, который вы передаете, опрашивается (используя рефлексию, не имеет значения) на предмет реализации своего собственного writeObject( ). Если это так, то нормальный процесс сериализации пропускается, и вызывается writeObject( ). Аналогичная ситуация наблюдается и для readObject( ).

Есть еще один поворот. Внутри вашего writeObject( ) вы можете выбрать выполнение стандартного действия writeObject( ), вызвав defaultWriteObject( ). Точно так же, внутри readObject( ) вы можете вызвать defaultReadObject( ). Вот пример, который демонстрирует, как вы можете управлять хранением и восстановлением объектов с интерфейсом Serializable:

//: c11:SerialCtl.java

// Управление сериализацией, путем добавления

// собственных методов writeObject() и readObject().

import java.io.*;

public class SerialCtl implements Serializable { String a; transient String b; public SerialCtl(String aa, String bb) { a = "Not Transient: " + aa; b = "Transient: " + bb; } public String toString() { 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.readObject(); } public static void main(String[] args) throws IOException, ClassNotFoundException { SerialCtl sc = new SerialCtl("Test1", "Test2"); System.out.println("Before:\n" + sc); ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream o = new ObjectOutputStream(buf); o.writeObject(sc); // Теперь получим это назад:

ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream( buf.toByteArray())); SerialCtl sc2 = (SerialCtl)in.readObject(); System.out.println("After:\n" + sc2); } } ///:~

В этом примере есть одно обычное поле String, а другое имеет модификатор transient, для обеспечения возможности сохранения не transient поля с помощью метода defaultWriteObject( ), а transient поля сохраняются и восстанавливаются явно. Поля инициализируются внутри конструктора, а не в точке определения, чтобы удостоверится, что они не инициализируются каким-либо автоматическим механизмом во время десериализации.



Если вы будете использовать стандартный механизм записи не transient частей вашего объекта, вы должны вызвать defaultWriteObject( ), как первое действие writeObject( ) и defaultReadObject( ), как первое действие readObject( ). Это странный вызов методов. Он может показать, например, что вы вызываете defaultWriteObject( ) для ObjectOutputStream и не передаете ему аргументов, но все же как-то происходит включение и узнавание ссылки на ваш объект и способа записи всех не transient частей. Мираж.

Для хранения и восстановления transient объектов используется более знакомый код. И еще, подумайте о том, что происходит тут. В main( ) создается объект SerialCtl, а затем он сериализуется в ObjectOutputStream. (Обратите внимание, что в этом случае используется буфер вместо файла — это все тот же ObjectOutputStream.) Сериализация происходит в строке:

o.writeObject(sc);

Метод writeObject( ) должен проверить sc на предмет существования собственного метода writeObject( ). (Не с помощью проверки интерфейса — здесь нет его — или типа класса, а реальной охотой за методом, используя рефлексию.) Если метод существует, он используется. Аналогичный подход используется для readObject( ). Возможно это чисто практический способ, которым можно решить проблему, но он, несомненно, странен.


Содержание раздела