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.
Si el objeto serializado tiene campos inmutables que necesitan serialización personalizada, la solución original writeObject/readObject
es 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 head
campo de cada enlace, seguido de un null
valor. Sin embargo, la deserialización de un formato de este tipo se vuelve imposible: readObject
no se pueden cambiar los valores de los campos miembros (ahora fijados en null
). Aquí viene el writeReplace
/ readResolve
par:
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.
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.List
implementació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 ArrayList
y devolverlo directamente en lugar de envolverlo en nuestra clase de vista.
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.
String.CaseInsensitiveComparator.readResolve()