Sei que essa pergunta é realmente antiga e tem uma resposta aceita, mas, como aparece muito na pesquisa do Google, pensei em pesar porque nenhuma resposta fornecida cobre os três casos que considero importantes - na minha opinião, o principal uso desses métodos. Obviamente, todos assumem que há realmente uma necessidade de formato de serialização personalizado.
Tome, por exemplo, classes de coleção. A serialização padrão de uma lista vinculada ou de um BST resultaria em uma enorme perda de espaço com muito pouco ganho de desempenho em comparação com apenas a serialização dos elementos em ordem. Isso é ainda mais verdadeiro se uma coleção é uma projeção ou uma exibição - mantém uma referência a uma estrutura maior do que expõe por sua API pública.
Se o objeto serializado tiver campos imutáveis que precisam de serialização personalizada, a solução original de writeObject/readObject
é insuficiente, pois o objeto desserializado é criado antes de ler a parte do fluxo gravada writeObject
. Tome esta implementação mínima de uma 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...
}
Essa estrutura pode ser serializada escrevendo recursivamente o head
campo de cada link, seguido por um null
valor. A desserialização de um formato desse tipo se torna impossível: readObject
não é possível alterar os valores dos campos dos membros (agora fixados em null
). Aí vem o par writeReplace
/ readResolve
:
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;
}
}
}
Lamento se o exemplo acima não compila (ou funciona), mas espero que seja suficiente para ilustrar meu argumento. Se você acha que este é um exemplo muito abrangente, lembre-se de que muitas linguagens funcionais são executadas na JVM e essa abordagem se torna essencial no caso deles.
Podemos querer realmente desserializar um objeto de uma classe diferente da que escrevemos para o ObjectOutputStream
. Este seria o caso de visualizações como uma java.util.List
implementação de lista que expõe uma fatia de uma mais longa ArrayList
. Obviamente, serializar toda a lista de apoio é uma má idéia e devemos escrever apenas os elementos da fatia exibida. Por que parar com isso e ter um nível inútil de indireção após a desserialização? Poderíamos simplesmente ler os elementos do fluxo em um ArrayList
e retorná-lo diretamente, em vez de agrupá-lo em nossa classe view.
Como alternativa, ter uma classe delegada semelhante dedicada à serialização pode ser uma opção de design. Um bom exemplo seria reutilizar nosso código de serialização. Por exemplo, se tivermos uma classe de construtor (semelhante ao StringBuilder para String), podemos escrever um delegado de serialização que serialize qualquer coleção gravando um construtor vazio no fluxo, seguido pelo tamanho da coleção e pelos elementos retornados pelo iterador da coleção. A desserialização envolveria a leitura do construtor, o acréscimo de todos os elementos de leitura subsequentes e o retorno do resultado final build()
dos delegados readResolve
. Nesse caso, precisaríamos implementar a serialização apenas na classe raiz da hierarquia de coleções, e nenhum código adicional seria necessário para implementações atuais ou futuras, desde que implementassem o resumoiterator()
ebuilder()
método (o último para recriar a coleção do mesmo tipo - o que seria um recurso muito útil por si só). Outro exemplo seria ter uma hierarquia de classes cujo código não controlamos totalmente - nossa (s) classe (s) base (s) de uma biblioteca de terceiros podem ter qualquer número de campos particulares que não sabemos nada sobre o qual podemos mudar de uma versão para outra, quebrando nossos objetos serializados. Nesse caso, seria mais seguro gravar os dados e reconstruir o objeto manualmente na desserialização.
String.CaseInsensitiveComparator.readResolve()