Serialización de Java: readObject () vs. readResolve ()


127

El libro Effective Java y otras fuentes proporcionan una explicación bastante buena sobre cómo y cuándo usar el método readObject () cuando se trabaja con clases serializables de Java. El método readResolve (), por otro lado, sigue siendo un misterio. Básicamente, todos los documentos que encontré mencionan solo uno de los dos o mencionan ambos solo individualmente.

Las preguntas que quedan sin respuesta son:

  • ¿Cuál es la diferencia entre los dos métodos?
  • ¿Cuándo se debe implementar qué método?
  • ¿Cómo se debe usar readResolve (), especialmente en términos de devolver qué?

Espero que puedas arrojar algo de luz sobre este asunto.


Ejemplo del JDK de Oracle:String.CaseInsensitiveComparator.readResolve()
kevinarpe

Respuestas:


138

readResolvese usa para reemplazar el objeto leído de la secuencia. El único uso que he visto para esto es imponer singletons; cuando se lee un objeto, reemplácelo con la instancia singleton. Esto garantiza que nadie pueda crear otra instancia serializando y deserializando el singleton.


3
Hay varias formas para que el código malicioso (o incluso los datos) lo eviten.
Tom Hawtin - tackline

66
Josh Bloch habla sobre las condiciones bajo las cuales esto se rompe en la efectiva Java 2nd ed. Punto 77. Menciona esto en esta charla que dio en Google IO hace un par de años (algunas veces hacia el final de la charla): youtube.com/watch?v=pi_I7oD_uGI
calvinkrishy

17
Esta respuesta me parece un poco inadecuada, ya que no menciona los transientcampos. readResolvese usa para resolver el objeto después de leerlo. Un ejemplo de uso es que quizás un objeto contiene algo de caché que se puede recrear a partir de datos existentes y no necesita ser serializado; los datos almacenados en caché pueden declararse transienty readResolve()reconstruirse después de la deserialización. Para eso es para lo que sirve este método.
Jason C

2
@JasonC su comentario de que "este tipo de cosas [manejo transitorio] es para lo que sirve este método " es engañoso. Consulte el documento de Java para Serializable: dice "Las clases que necesitan designar un reemplazo cuando se lee una instancia del flujo deben implementar este readResolvemétodo especial [ ] ...".
Opher

2
El método readResolve también se puede usar en un caso de esquina en el que supongamos que ha serializado muchos objetos y los ha almacenado en la base de datos. Si en un momento posterior desea migrar esos datos a un nuevo formato, puede lograrlo fácilmente en el método readResolve.
Nilesh Rajani

29

Artículo 90, Java efectivo, portadas de 3.a ed readResolvey writeReplacepara servidores proxy en serie: su uso principal. Los ejemplos no escriben readObjectni writeObjectmétodos porque están utilizando la serialización predeterminada para leer y escribir campos.

readResolvese llama después de que readObjectha regresado (por el contrario, writeReplacese llama antes writeObjecty probablemente en un objeto diferente). El objeto que devuelve el método reemplaza el thisobjeto devuelto al usuario ObjectInputStream.readObjecty cualquier otra referencia posterior al objeto en la secuencia. Ambos readResolvey writeReplacepueden devolver objetos del mismo tipo o diferentes. Devolver el mismo tipo es útil en algunos casos donde los campos deben estar finaly se requiere compatibilidad con versiones anteriores o se deben copiar y / o validar los valores.

El uso de readResolveno impone la propiedad singleton.


9

readResolve se puede usar para cambiar los datos que se serializan a través del método readObject. Por ejemplo, xstream API usa esta función para inicializar algunos atributos que no estaban en el XML para ser deserializados.

http://x-stream.github.io/faq.html#Serialization


1
XML y Xstream no son relevantes para una pregunta sobre la serialización de Java, y la pregunta se respondió correctamente hace años. -1
Marqués de Lorne

55
La respuesta aceptada establece que readResolve se usa para reemplazar un objeto. Esta respuesta proporciona la información adicional útil que se puede utilizar para modificar un objeto durante la deserialización. XStream se dio como ejemplo, no como la única biblioteca posible en la que eso sucede.
Enviado

5

readResolve es para cuando necesite devolver un objeto existente, por ejemplo, porque está buscando entradas duplicadas que deberían fusionarse, o (por ejemplo, en sistemas distribuidos eventualmente consistentes) porque es una actualización que puede llegar antes de darse cuenta de Cualquier versión anterior.


readResolve () fue claro para mí, pero aún tengo algunas preguntas inexplicables en mente, pero su respuesta solo leyó mi mente, gracias
Rajni Gangwar

5

readObject () es un método existente en la clase ObjectInputStream . Mientras lee el objeto en el momento de la deserialización, el método readObject verifica internamente si el objeto de clase que se deserializa con el método readResolve o no, si existe el método readResolve, invocará el método readResolve y devolverá el mismo ejemplo.

Por lo tanto, la intención de escribir el método readResolve es una buena práctica para lograr un patrón de diseño singleton puro en el que nadie pueda obtener otra instancia serializando / deserializando.



2

Cuando se utiliza la serialización para convertir un objeto para que se pueda guardar en un archivo, podemos activar un método, readResolve (). El método es privado y se mantiene en la misma clase cuyo objeto se recupera durante la deserialización. Asegura que después de la deserialización, qué objeto se devuelve sea el mismo que se serializó. Es decir,instanceSer.hashCode() == instanceDeSer.hashCode()

El método readResolve () no es un método estático. Después in.readObject()se llama durante la deserialización, solo se asegura de que el objeto devuelto sea el mismo que se serializó como se muestra a continuación mientrasout.writeObject(instanceSer)

..
    ObjectOutput out = new ObjectOutputStream(new FileOutputStream("file1.ser"));
    out.writeObject(instanceSer);
    out.close();

De esta manera, también ayuda en la implementación del patrón de diseño singleton , porque cada vez que se devuelve la misma instancia.

public static ABCSingleton getInstance(){
    return ABCSingleton.instance; //instance is static 
}

1

Sé que esta pregunta es muy antigua y tiene una respuesta aceptada, pero como aparece muy alto en la búsqueda de Google, pensé en opinar porque ninguna respuesta proporcionada cubre los tres casos que considero importantes, en mi opinión, el uso principal de estos métodos. Por supuesto, todos asumen que en realidad es necesario un formato de serialización personalizado.

Tomemos, por ejemplo, clases de colección. La serialización predeterminada de una lista vinculada o un BST daría como resultado una gran pérdida de espacio con muy poca ganancia de rendimiento en comparación con solo serializar los elementos en orden. Esto es aún más cierto si una colección es una proyección o una vista: mantiene una referencia a una estructura más grande que la que expone su API pública.

  1. Si el objeto serializado tiene campos inmutables que necesitan serialización personalizada, la solución original writeObject/readObjectes insuficiente, ya que el objeto deserializado se crea antes de leer la parte de la secuencia escrita writeObject. Tome esta implementación mínima de una lista vinculada:

    public class List<E> extends Serializable {
        public final E head;
        public final List<E> tail;
    
        public List(E head, List<E> tail) {
            if (head==null)
                throw new IllegalArgumentException("null as a list element");
            this.head = head;
            this.tail = tail;
        }
    
        //methods follow...
    }

Esta estructura se puede serializar escribiendo recursivamente el headcampo de cada enlace, seguido de un nullvalor. Sin embargo, la deserialización de un formato de este tipo se vuelve imposible: readObjectno se pueden cambiar los valores de los campos miembros (ahora fijados en null). Aquí viene el writeReplace/ readResolvepar:

private Object writeReplace() {
    return new Serializable() {
        private transient List<E> contents = List.this;

        private void writeObject(ObjectOutputStream oos) {
            List<E> list = contents;
            while (list!=null) {
                oos.writeObject(list.head);
                list = list.tail;
            }
            oos.writeObject(null);
        }

        private void readObject(ObjectInputStream ois) {
            List<E> tail = null;
            E head = ois.readObject();
            if (head!=null) {
                readObject(ois); //read the tail and assign it to this.contents
                this.contents = new List<>(head, this.contents)
            }                     
        }


        private Object readResolve() {
            return this.contents;
        }
    }
}

Lo siento si el ejemplo anterior no se compila (o funciona), pero espero que sea suficiente para ilustrar mi punto. Si cree que este es un ejemplo muy descabellado, recuerde que muchos lenguajes funcionales se ejecutan en la JVM y este enfoque se vuelve esencial en su caso.

  1. Es posible que queramos deserializar un objeto de una clase diferente de la que le escribimos ObjectOutputStream. Este sería el caso con vistas como una java.util.Listimplementación de lista que expone una porción de una más larga ArrayList. Obviamente, serializar toda la lista de respaldo es una mala idea y solo debemos escribir los elementos del segmento visto. Sin embargo, ¿por qué detenerse y tener un nivel inútil de indirección después de la deserialización? Simplemente podríamos leer los elementos de la secuencia en un ArrayListy devolverlo directamente en lugar de envolverlo en nuestra clase de vista.

  2. Alternativamente, tener una clase delegada similar dedicada a la serialización puede ser una opción de diseño. Un buen ejemplo sería reutilizar nuestro código de serialización. Por ejemplo, si tenemos una clase de constructor (similar al StringBuilder for String), podemos escribir un delegado de serialización que serialice cualquier colección escribiendo un generador vacío en la secuencia, seguido del tamaño de la colección y los elementos devueltos por el iterador de la colección. La deserialización implicaría leer el constructor, agregar todos los elementos leídos posteriormente y devolver el resultado final build()de los delegadosreadResolve . En ese caso, tendríamos que implementar la serialización solo en la clase raíz de la jerarquía de recopilación, y no se necesitaría ningún código adicional de las implementaciones actuales o futuras, siempre que implementen resumeniterator() ybuilder()método (este último para recrear la colección del mismo tipo, que sería una característica muy útil en sí misma). Otro ejemplo sería tener una jerarquía de clases cuyo código no controlamos completamente: nuestras clases base de una biblioteca de terceros podrían tener cualquier número de campos privados de los que no sabemos nada y que pueden cambiar de una versión a otra, interrumpiendo Nuestros objetos serializados. En ese caso, sería más seguro escribir los datos y reconstruir el objeto manualmente en la deserialización.


0

El método readResolve

Para las clases serializables y externalizables, el método readResolve permite que una clase reemplace / resuelva el objeto leído de la secuencia antes de que se devuelva al llamante. Al implementar el método readResolve, una clase puede controlar directamente los tipos e instancias de sus propias instancias que se deserializan. El método se define de la siguiente manera:

CUALQUIER MODIFICADOR DE ACCESO Object readResolve () lanza ObjectStreamException;

Se llama al método readResolve cuando ObjectInputStream ha leído un objeto de la secuencia y se está preparando para devolverlo al llamante. ObjectInputStream comprueba si la clase del objeto define el método readResolve. Si se define el método, se llama al método readResolve para permitir que el objeto en la secuencia designe el objeto que se devolverá. El objeto devuelto debe ser de un tipo que sea compatible con todos los usos. Si no es compatible, se lanzará una ClassCastException cuando se descubra la falta de coincidencia de tipos.

Por ejemplo, se podría crear una clase Symbol para la que solo existiera una instancia de cada enlace de símbolo dentro de una máquina virtual. El método readResolve se implementaría para determinar si ese símbolo ya estaba definido y sustituir el objeto Symbol equivalente preexistente para mantener la restricción de identidad. De esta manera, la singularidad de los objetos Symbol se puede mantener a través de la serialización.


0

Como ya se respondió, readResolvees un método privado utilizado en ObjectInputStream mientras se deserializa un objeto. Esto se llama justo antes de que se devuelva la instancia real. En el caso de Singleton, aquí podemos forzar el retorno de la referencia de instancia de singleton ya existente en lugar de la referencia de instancia deserializada. De manera similar, tenemos writeReplacepara ObjectOutputStream.

Ejemplo para readResolve:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SingletonWithSerializable implements Serializable {
private static final long serialVersionUID = 1L;

public static final SingletonWithSerializable INSTANCE = new SingletonWithSerializable();

private SingletonWithSerializable() {
    if (INSTANCE != null)
        throw new RuntimeException("Singleton instance already exists!");
}

private Object readResolve() {
    return INSTANCE;
}

public void leaveTheBuilding() {
    System.out.println("SingletonWithPublicFinalField.leaveTheBuilding() called...");
}

public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
    SingletonWithSerializable instance = SingletonWithSerializable.INSTANCE;

    System.out.println("Before serialization: " + instance);

    try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("file1.ser"))) {
        out.writeObject(instance);
    }

    try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("file1.ser"))) {
        SingletonWithSerializable readObject = (SingletonWithSerializable) in.readObject();
        System.out.println("After deserialization: " + readObject);
    }

}

}

Salida:

Before serialization: com.ej.item3.SingletonWithSerializable@7852e922
After deserialization: com.ej.item3.SingletonWithSerializable@7852e922
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.