Почему readObject и writeObject являются частными и почему я должен записывать переходные переменные явно?

Я читаю главу о сериализации в эффективной Java.

  1. Кто вызывает readObject() и writeObject()? Почему эти методы объявлены закрытыми?

  2. Ниже приведен фрагмент кода из книги

    // 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.

Другие вопросы по тегам