java serialization effective-java

java - ¿Por qué son readObject y writeObject privados, y por qué debería escribir variables transitorias de forma explícita?



serialization effective-java (6)

(1) Los métodos no están declarados en ninguna clase o interfaz. Una clase que implementa la interfaz Serializable y requiere un manejo especial especial durante el proceso de serialización y deserialización debe implementar esos métodos y el serializador / deserializador intentará reflejar esos métodos.

Esta es una de las esquinas más bien extrañas en Java donde la API está realmente definida en el javaDoc ... Pero si los métodos se habían definido en una interfaz, entonces tenían que ser public (no podemos implementar un método de interfaz como un bloqueo al agregar un modificador private ).

¿Por qué privado ? El javaDoc no da una pista. Tal vez se especifiquen como privados porque ninguna otra clase, salvo el implementador, tiene la intención de usarlos. Son privados por definición .

(2) El ejemplo simplemente muestra cómo funciona el manejo especial. En este ejemplo, el size es transitorio y no se serializará. Pero ahora presentamos el controlador especial y este controlador agrega el valor de size a la transmisión. La diferencia con el enfoque normal con campos no transitorios podría ser el orden de los elementos en la secuencia resultante (si es que importa ...).

El ejemplo podría tener sentido si el campo transitorio se definió en una superclase y una subclase quería serializar el valor.

Estoy leyendo el capítulo sobre Serialización en Java Efectivo .

  1. ¿Quién llama a readObject () y writeObject ()? ¿Por qué estos métodos se declaran privados?

  2. El siguiente es un fragmento de código del libro

    // 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); } } }

    ¿Hay alguna razón específica por la cual el size variable se declara como transitorio y luego en el método writeObject está escrito explícitamente? Si no se hubiera declarado como transitorio, se habría escrito de todos modos, ¿no?


Acerca de readObject () / writeObject () siendo privado, este es el trato: si su clase Bar amplía alguna clase Foo; Foo también implementa readObject () / writeObject () y Bar también implementa readObject () / writeObject ().

Ahora, cuando un objeto Bar es serializado o deserializado, JVM necesita llamar a readObject () / writeObject () tanto para Foo como para Bar automáticamente (es decir, sin necesidad de clasificar estos métodos de superclase explícitamente). Sin embargo, si estos métodos son cualquier cosa menos privado, se convierte en método anulado, y JVM ya no puede llamar a los métodos de clase superior en el objeto de clase secundaria.

Por lo tanto, deben ser privados!


Además , no debe ser utilizado por partes equivocadas , aquí hay otra razón para la privacidad de estos métodos:

No deseamos que estos métodos sean anulados por subclases . En cambio, cada clase puede tener su propio método writeObject , y el motor de serialización los llamará a todos uno tras otro. Esto solo es posible con métodos privados (estos no se anulan). (Lo mismo es válido para readObject ).

(Tenga en cuenta que esto solo se aplica a las superclases que a su vez implementan Serializable).

De esta forma, las subclases y las superclases pueden evolucionar de forma independiente y seguir siendo compatibles con los objetos almacenados de versiones anteriores.


Con respecto a la variable transitoria, la mejor manera de entender como si por qué declaramos la variable transitoria y luego serializarlos en el método writeobject es verificar / analizar / depurar los métodos readobject / writeobject de las clases LinkedList / HashMap / etc.

Esto generalmente se hace cuando desea serializar / deserializar las variables de clase en un orden predefinido y no depender del comportamiento / orden predeterminado.


Supongamos que tiene una clase A que hace referencia a un zócalo. Si desea serializar objetos de clase A, no puede hacerlo directamente porque Socket no se puede serializar. En este caso, escriba el código como se indica a continuación.

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); } }

Ignore cualquier problema relacionado con la red ya que el propósito es mostrar el uso de los métodos writeObject / readObject.


readObject y writeObject son llamados por la (s) clase (s) Object(Input/Output)Stream del Object(Input/Output)Stream ).

Estos métodos son (y deben) declararse privados (al implementar los suyos), lo que demuestra / indica que ninguno de los métodos es heredado y anulado o sobrecargado por la implementación. El truco aquí es que la JVM comprueba automáticamente si se declara cualquiera de los métodos durante la llamada al método correspondiente. Tenga en cuenta que la JVM puede llamar a métodos privados de su clase siempre que lo desee, pero ningún otro objeto puede hacerlo. Por lo tanto, se mantiene la integridad de la clase y el protocolo de serialización puede seguir funcionando de forma normal.

Y con respecto a la int transitoria, es solo tomar el control sobre la serialización de la serialización de objetos completos como tal. Sin embargo, tenga en cuenta que técnicamente ni siquiera es necesario invocar defaultWriteObject() , si todos los campos son transitorios. Pero creo que todavía se recomienda invocarlo para fines de flexibilidad, para que luego pueda introducir miembros no transitorios en su clase, preservando la compatibilidad.