Почему readObject и writeObject являются частными и почему я должен записывать переходные переменные явно?
Я читаю главу о сериализации в эффективной Java.
Кто вызывает readObject() и writeObject()? Почему эти методы объявлены закрытыми?
Ниже приведен фрагмент кода из книги
// StringList with a reasonable custom serialized form public final class StringList implements Serializable { private transient int size = 0; private transient Entry head = null; //Other code private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); s.writeInt(size); // Write out all elements in the proper order. for (Entry e = head; e != null; e = e.next) s.writeObject(e.data); } } }
Есть ли конкретная причина, по которой переменная
size
объявляется как переходный процесс, а затем в методе writeObject это явно написано? Если бы он не был объявлен как временный, он все равно был бы написан, верно?
6 ответов
(1) Методы не объявлены ни в одном классе или интерфейсе. Класс, который реализует Serializable
интерфейс и требует специальной специальной обработки в процессе сериализации и десериализации, должны реализовать эти методы, и сериализатор / десериализатор будет пытаться отразить эти методы.
Это один из довольно странных углов в Java, где API фактически определен в javaDoc... Но если методы были определены в интерфейсе, то они должны были быть public
(мы не можем реализовать метод интерфейса, заблокировать его, добавив private
модификатор).
Почему приватный - javaDoc не дает подсказки. Возможно, они определены как закрытые, потому что никакой другой класс, кроме разработчика, не предназначен для их использования. Они являются частными по определению.
(2) В примере просто показано, как работает специальная обработка. В этом примере size
является переходным процессом и не будет сериализован. Но теперь мы представляем специальный обработчик, и этот обработчик добавляет значение size
в поток. Отличие от обычного подхода с нестационарными полями может заключаться в порядке элементов в результирующем потоке (если это имеет значение...).
Пример мог бы иметь смысл, если бы временное поле было определено в суперклассе, а подкласс хотел сериализовать значение.
Помимо того, что не должны использоваться неправильными сторонами, вот еще одна причина конфиденциальности этих методов:
Мы не хотим, чтобы эти методы были переопределены подклассами. Вместо этого у каждого класса может быть свой writeObject
метод, и механизм сериализации будет вызывать их всех один за другим. Это возможно только с закрытыми методами (они не переопределяются). (То же самое верно для readObject
.)
(Обратите внимание, что это относится только к суперклассам, которые сами реализуют Serializable.)
Таким образом, подклассы и суперклассы могут развиваться независимо и при этом оставаться совместимыми с сохраненными объектами более старых версий.
О том, что readObject()/writeObject() является приватным, вот в чем дело: если ваш класс Bar расширяет некоторый класс Foo; Foo также реализует readObject()/writeObject(), а Bar также реализует readObject()/writeObject().
Теперь, когда объект Bar сериализован или десериализован, JVM необходимо автоматически вызвать readObject()/writeObject() для Foo и Bar (т.е. без необходимости явной классификации этих методов суперкласса). Однако, если эти методы не являются приватный, он переопределяет методы, и JVM больше не может вызывать методы суперкласса для объекта подкласса.
Следовательно, они должны быть частными!
readObject
а также writeObject
называются Object(Input/Output)Stream
класс (ы).
Эти методы (и должны быть) объявлены закрытыми (при реализации ваших собственных), доказывая / указывая, что ни один из методов не наследуется и не переопределяется или перегружается реализацией. Хитрость в том, что JVM автоматически проверяет, объявлен ли какой-либо метод во время соответствующего вызова метода. Обратите внимание, что JVM может вызывать закрытые методы вашего класса в любое время, но другие объекты этого не могут. Таким образом, целостность класса сохраняется, и протокол сериализации может продолжать работать в обычном режиме.
И относительно переходного int
это просто контроль над сериализацией всей сериализации объекта как таковой. Тем не менее, обратите внимание, что технически это даже не нужно вызывать defaultWriteObject()
, если все поля переходные. Но я думаю, что все еще рекомендуется вызывать его для целей гибкости, чтобы позже вы могли вводить в своем классе непереходные члены, сохраняя совместимость.
Что касается временной переменной, лучший способ понять, как будто мы объявляем переменную переменную, а затем сериализуем их в методе writeobject, - это проверить / проанализировать / отладить методы readobject / writeobject классов LinkedList / HashMap / etc.
Обычно это делается, когда вы хотите сериализовать / десериализовать переменные класса в предопределенном порядке, а не полагаться на поведение / порядок по умолчанию.
Предположим, у вас есть класс A, который имеет ссылку на сокет. Если вы хотите сериализовать объекты класса A, вы не можете напрямую, потому что Socket не Serializable . В этом случае вы пишете код, как показано ниже.
public class A implements implements Serializable {
// mark Socket as transient so that A can be serialized
private transient Socket socket;
private void writeObject(ObjectOutputStream out)throws IOException {
out.defaultWriteObject();
// take out ip address and port write them to out stream
InetAddress inetAddress = socket.getInetAddress();
int port = socket.getPort();
out.writeObject(inetAddress);
out.writeObject(port);
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException{
in.defaultReadObject();
// read the ip address and port form the stream and create a frest socket.
InetAddress inetAddress = (InetAddress) in.readObject();
int port = in.readInt();
socket = new Socket(inetAddress, port);
}
}
Проигнорируйте любые проблемы, связанные с сетью, поскольку цель состоит в том, чтобы показать использование методов writeObject/ readObject.