Effective Java tmprogramming Language GuideJ o s h u a b lo c h
Скачать 1.05 Mb.
|
; // Состояние private boolean initialized = false;protected final int getY() { return y; } // Должен вызываться для всех открытых методов экземпляра private void checkInit() throws IllegalStateException { if (!initialized) throw new IllegalStateException("Uninitialized"); } // ... // Остальное опущено } Все методы в экземпляре AbstractFoo, прежде чем выполнять свою работу, должны вызывать checklnit. Тем самым гарантируется быстрое и четкое аварийное завершение этих методов в случае, если неудачно написанный подкласс не инициализировал соответствующий экземпляр. Имея этот механизм взамен прежнего, можно перейти к реализации сериалнзуемого подкласса import java.io.*; public class Foo extends AbstractFoo implements Serializable { private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); // Ручная десериализация и инициализация состояния суперкласса int x = s.readInt(); int y = s.readInt(); initialize(x, y); } private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); // Ручная сериализация состояния суперкласса s.writeInt(getX()); s.writeInt(getY()); } // Конструктор не использует никаких причудливых механизмов public Foo(int x, int y) { super(x, y); Внутренние классы статья 18) редко должны (если вообще должны) реализовывать интерфейс Serializable. Для размещения ссылок на экземпляры контейнера (enclosing instance) и значений локальных переменных из окружения они 203 Сериализованная форма представления объекта, предлагаемая по умолчанию,это довольно Эффективное физическое представление графа объектов, имеющего корнем данный объект. Другими словами, эта форма описывает данные, содержащиеся как в самом объекте, таки во всех доступных из него объектах. Она также отражает топологию взаимосвязи этих объектов. Идеальная же сериализованная форма, описывающая объект, содержит только представляемые им логические данные, От физического представления она не зависит, 204 // Ужасный кандидат на использование сериализованной формы, // предлагаемой по умолчанию public class StringList implements Serializable { private int size = 0; private Entry head = null; private static class Entry implements Serializable { String data; Entry next; Entry previous; } // Остальное опущено Логически этот класс представляет последовательность строк. Физически последовательность представлена им как дважды связный список. Если вы примите сериализованную форму, предлагаемую по умолчанию, она старательно отразит каждый элемент в этом связном списке, а также все связи между этими элементами в обоих направлениях. В случае, когда физическое представление объекта существенно отличается от содержащихся в нем логических данных, сериализованная форма, предлагаемая по умолчанию, имеет четыре недостатка Она навсегда связывает внешний API класса сего текущим внутренним представлением. В приведенном примере закрытый класс StringList.Entry становится частью открытого API. Даже если в будущей версии внутреннее представление StringList поменяется, он все равно должен будет получать на входе представление в виде связного списка и генерировать его жена выходе. Этот класс уже никогда не избавится от кода, необходимого для манипулирования связными списками, даже если он ими уже не пользуется. Она может занимать чрезвычайно много места. В приведенном примере в сериализованной форме без всякой на то надобности представлен каждый элемент связанного списка со всеми его связями. Эти элементы и связи являются всего лишь деталями реализации, нестоящими включения в сериализованную форму. Из-за того, что полученная форма слишком велика, ее запись на диск или передача посети будет выполняться слишком медленно. 206 // Класс StringList справильной сериализованной формой public class StringList implements Serializable private transient int size = о private transient Entry head = null; // Больwе нет реализации Serializablel private static class Entry { String data; Entry next; Entry previous; } // Добавляет указанную строку вконец списка public void add(String s) { ... } /** * Сериализует данный экземпляр StringList. * * @serialData Показывается размер списка (количество * содержащихся в нем строк) «tt>int * элементы списка (каждый в виде String 206 s.w ritelnt(size); // Выписываем все элементы в правильном порядке for (Entry ее е = e.next) s.w riteObject(e.data); } private void readObject(ObjectlnputStream s) throw s IOException, Cl assNotFoundException { s.defaultReadObject(); int size = s. readlnt(); // Считываем все элементы и вставляем for (int i = 0; i < size; i++) add((String)s. readObject()) ; } // Остальное опускаем Заметим, что из метода writeObject вызывается defaultWriteObject, а из метода readObject делается вызов defaultReadObject, несмотря на то, что ни одно из полей класса StringList не попадает в сериализованную форму. Если все экземпляры полей имеют модификатор transient, то формально можно обойтись без вызова методов defaultWriteObject и defaultReadObject, но это не рекомендуется. Даже если все экземпляры полей имеют модификатор trаnsiеnt, вызов defaultWriteObject оказывает влияние на сериализованную форму, в результате чего значительно повышается гибкость сериализации. Полученная форма оставляет возможность в последующих версиях добавлять в форму новые экземпляры полей, сохраняя при этом прямую и обратную совместимость с предыдущими версиями. Так, если сериализовать экземпляр класса в более поздней версии, а десериализовать в более ранней версии, появившиеся поля будут проигнорированы. Если бы более ранняя версия метода readObject не вызывала метод defaultReadObject, десериализация закончилась бы инициированием StreamCorruptedException. Заметим также, что хотя метод writeObject является закрытым, он сопровождается комментариями к документации. Объяснение здесь тоже, что ив случае с комментариями для закрытых полей в классе Name. Этот закрытый метод определяет сериализованную форму открытый API, а открытый API должен быть описан в документации. Как и тег @seria1 в случае с полями, тег @serialData для методов говорит утилите Javadoc о том, что данную информацию необходимо поместить на страницу с описанием сериализованных форм. 208 Сериализованная форма, предлагаемая по умолчанию, плохо подходит для класса StringList, но есть классы, для которых она подходит еще меньше. Для StгiпgList сериализованная форма, применяемая по умолчанию, не имеет гибкости и работает медленно. Однако она является правильнои в том смысле, что в результате сериализации и десериализации экземпляра StringList получается точная копия исходного объекта, и все его инварианты будут сохранены. Но для любого объекта, чьи инварианты привязаны к деталям реализации, это не так. Например, рассмотрим случай с хэш-таблицей. Ее физическим представлением является набор сегментов, содержащих записи ключ/значение. Сегмент, куда будет помещена запись, определяется функцией, которая для представленного ключа вычисляет хэш-код. Вообще говоря, нельзя гарантировать, что в различных реализациях jVM эта функция будет одной и той же. В действительности нельзя даже гарантировать, что она будет оставаться той же самой, если одну и туже запускать несколько раз. Следовательно, использование для хэш-таблицы сериализованной формы, предлагаемой по умолчанию, может стать серьезной ошибкой сериализация и десериализация хэш-таблицы могут привести к созданию объекта, инварианты которого будут серьезно нарушены. 11спользуете вы или нет сериализованную форму, предлагаемую по умолчанию, каждый экземпляр поляне помеченный модификатором trаnsiеnt, будет сериализован при вызове метода defaultWriteObject. Поэтому каждое поле, которое можно не заносить в форму, нужно пометить этим модификатором. К таковым относятся избыточные поля, чьи значения можно вычислить по таким "первичным полям данных, как кэшированное значение хэша. Сюда также относятся поля, чьи значения меняются при повторном запуске JVM. Например, это может быть поле типа 10ng, в котором хранится указатель на местную' (native) структуру данных. Прежде чем согласиться на запись какого- либо поля в сериализованной форме, убедитесь в том, что его значение является частью логического состояния данного объекта. Если вы пользуетесь специальной сериализованной формой, большинство или даже все экземпляры полей нужно пометить модификатором trаnsiеnt, как в примере с классом StringList. Если вы пользуетесь сериализованной формой, предлагаемой по умолчанию, и к тому же пометили одно или несколько полей как trаnsiеnt, помните о том, что при десериализации экземпляра эти поля получат значения по умолчанию п для полей ссылок на объекты, нуль для простых числовых полей и false для полей типа 209 // Произвольное число типа long Неважно, какое значение вы выберете для randoтLongValиe. Общепринятая практика предписывает генерировать это число, запуская для класса утилиту serialver. Однако можно взять число просто из "воздуха. Если вам когда-нибудь захочется создать новую версию класса, которая несовместима с имеющимися версиями, достаточно будет поменять значение в этой декларации. В результате попытки десериализировать экземпляры, сериализованные в предыдущих версиях, будут заканчиваться инициированием исключения InvalidClassException. Подведем итоги. Если решено, что класс должен быть сериализуемым (статья 54), подумайте над тем, какой должна быть сериализованная форма. Форму, предлагаемую по умолчанию, используйте, только если она правильно описывает логическое состояние объекта. В противном случае создайте специальную сериализованную форму, которая надлежащим образом описывает этот объект. На разработку сериализованной формы для класса вы должны выделить не меньше времени, чем на разработку его методов, предоставляемых клиентам. Точно также, как из последующих версий нельзя изъять те методы класса, которые были доступны клиентам, нельзя изымать поля из сериализованной формы. Чтобы при сериализации сохранялась совместимость, эти поля должны оставаться в форме навсегда. Неверный выбор сериализованной формы может иметь постоянное отрицательное влияние на сложность и производительность класса. Метод должен создаваться с защитой В статье 24 представлен неизменяемый класс для интервалов времени, который содержит изменяемые закрытые поля даты. Чтобы сохранить свои инварианты и неизменяемость, этот класс создает резервную копию объектов Date в конструкторе и методах доступа. Приведем этот класс 210 |