Serialização Java: readObject () vs. readResolve ()

127

O livro Java Efetivo e outras fontes fornecem uma boa explicação sobre como e quando usar o método readObject () ao trabalhar com classes Java serializáveis. O método readResolve (), por outro lado, permanece um pouco misterioso. Basicamente, todos os documentos que encontrei mencionam apenas um dos dois ou mencionam os dois apenas individualmente.

As perguntas que permanecem sem resposta são:

  • Qual é a diferença entre os dois métodos?
  • Quando qual método deve ser implementado?
  • Como readResolve () deve ser usado, especialmente em termos de retorno de quê?

Espero que você possa lançar alguma luz sobre este assunto.

Forragem
fonte
Exemplo do JDK da Oracle:String.CaseInsensitiveComparator.readResolve()
kevinarpe

Respostas:

137

readResolveé usado para substituir o objeto lido do fluxo. O único uso que eu já vi para isso é impor singletons; quando um objeto for lido, substitua-o pela instância singleton. Isso garante que ninguém possa criar outra instância serializando e desserializando o singleton.

Michael Myers
fonte
3
Há várias maneiras de código malicioso (ou mesmo dados) contornar isso.
Tom Hawtin # tackline
6
Josh Bloch fala sobre as condições sob as quais isso quebra no efetivo Java 2nd ed. Item 77. Ele menciona isso nesta palestra que deu no Google IO há alguns anos (algumas vezes no final da palestra): youtube.com/watch?v=pi_I7oD_uGI
calvinkrishy 18/09/10
17
Acho essa resposta um pouco inadequada, pois não menciona transientcampos. readResolveé usado para resolver o objeto depois que ele é lido. Um exemplo de uso é talvez um objeto que armazene algum cache que possa ser recriado a partir de dados existentes e não precise ser serializado; os dados em cache podem ser declarados transiente readResolve()podem reconstruí-los após a desserialização. Coisas assim são para que serve esse método.
Jason C
2
@ JasonC, seu comentário de que "coisas desse tipo [tratamento transitório] são para que serve esse método " é enganoso. Consulte o documento Java para Serializable: ele diz "Classes que precisam designar uma substituição quando uma instância dela é lida a partir do fluxo devem implementar este [ readResolve] método especial ...".
Opher
2
O método readResolve também pode ser usado em uma caixa de canto em que suponha que você tenha serializado muitos objetos e os tenha armazenado no banco de dados. Se, posteriormente, você desejar migrar esses dados para um novo formato, poderá conseguir isso facilmente no método readResolve.
Nilesh Rajani
29

O item 90, Java efetivo, 3ª Ed, cobre readResolvee writeReplacepara proxies seriais - seu principal uso. Os exemplos não escrevem readObjecte writeObjectmétodos porque estão usando serialização padrão para ler e gravar campos.

readResolveé chamado depois readObjectque retornou (inversamente writeReplaceé chamado antes writeObjecte provavelmente em um objeto diferente). O objeto que o método retorna substitui o thisobjeto retornado ao usuário ObjectInputStream.readObjecte quaisquer outras referências anteriores ao objeto no fluxo. Ambos readResolvee writeReplacepodem retornar objetos do mesmo ou de tipos diferentes. Retornar o mesmo tipo é útil em alguns casos em que os campos devem estar finale a compatibilidade com versões anteriores é necessária ou os valores devem ser copiados e / ou validados.

O uso de readResolvenão impõe a propriedade singleton.

Tom Hawtin - linha de orientação
fonte
9

readResolve pode ser usado para alterar os dados que são serializados através do método readObject. Por exemplo, a API xstream usa esse recurso para inicializar alguns atributos que não estavam no XML a serem desserializados.

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

sem fim
fonte
1
XML e Xstream não são relevantes para uma pergunta sobre serialização Java, e a pergunta foi respondida corretamente anos atrás. -1
Marquês de Lorne
5
A resposta aceita afirma que readResolve é usado para substituir um objeto. Esta resposta fornece informações adicionais úteis que podem ser usadas para modificar um objeto durante a desserialização. O XStream foi dado como exemplo, não como a única biblioteca possível em que isso acontece.
Ligado em
5

readResolve é para quando você precisar retornar um objeto existente, por exemplo, porque você está verificando entradas duplicadas que devem ser mescladas ou (por exemplo, em sistemas distribuídos eventualmente consistentes), porque é uma atualização que pode chegar antes que você esteja ciente de quaisquer versões mais antigas.

Pr0methean
fonte
readResolve () ficou claro para mim, mas ainda tenho algumas dúvidas inexplicáveis em mente, mas a sua resposta apenas ler minha mente, graças
Rajni Gangwar
5

readObject () é um método existente na classe ObjectInputStream. enquanto lê o objeto no momento da desserialização, o método readObject verifica internamente se o objeto de classe que está sendo desserializado com o método readResolve ou se o método readResolve existe, então ele invocará o método readResolve e retornará o mesmo instância.

Portanto, a intenção de escrever o método readResolve é uma boa prática para atingir o padrão de design singleton puro, onde ninguém pode obter outra instância serializando / desserializando.

arjun kumar mehta
fonte
3

readResolve () garantirá o contrato de singleton durante a serialização.
Por favor consulte

Kanagavelu Sugumar
fonte
2

Quando a serialização é usada para converter um objeto para que ele possa ser salvo em arquivo, podemos acionar um método, readResolve (). O método é privado e é mantido na mesma classe cujo objeto está sendo recuperado durante a desserialização. Ele garante que, após a desserialização, qual objeto retornado seja o mesmo que foi serializado. Isso é,instanceSer.hashCode() == instanceDeSer.hashCode()

O método readResolve () não é estático. After in.readObject()é chamado durante a desserialização, apenas garante que o objeto retornado seja o mesmo que foi serializado como abaixo enquantoout.writeObject(instanceSer)

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

Dessa maneira, também ajuda na implementação do padrão de design de singleton , porque sempre que a mesma instância é retornada.

public static ABCSingleton getInstance(){
    return ABCSingleton.instance; //instance is static 
}
hi.nitish
fonte
1

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.

  1. 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 headcampo de cada link, seguido por um nullvalor. A desserialização de um formato desse tipo se torna impossível: readObjectnã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.

  1. 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.Listimplementaçã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 ArrayListe retorná-lo diretamente, em vez de agrupá-lo em nossa classe view.

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

Turim
fonte
0

O método readResolve

Para as classes Serializable e Externalizable, o método readResolve permite que uma classe substitua / resolva o objeto lido do fluxo antes de retornar ao chamador. Ao implementar o método readResolve, uma classe pode controlar diretamente os tipos e instâncias de suas próprias instâncias sendo desserializadas. O método é definido da seguinte maneira:

QUALQUER ACESSO-MODIFICADOR O objeto readResolve () lança ObjectStreamException;

O método readResolve é chamado quando ObjectInputStream lê um objeto do fluxo e está se preparando para devolvê-lo ao chamador. ObjectInputStream verifica se a classe do objeto define o método readResolve. Se o método for definido, o método readResolve será chamado para permitir que o objeto no fluxo designe o objeto a ser retornado. O objeto retornado deve ser de um tipo compatível com todos os usos. Se não for compatível, uma ClassCastException será lançada quando a incompatibilidade de tipo for descoberta.

Por exemplo, uma classe Symbol pode ser criada para a qual existia apenas uma instância de cada ligação de símbolo em uma máquina virtual. O método readResolve seria implementado para determinar se esse símbolo já foi definido e substituir o objeto Symbol equivalente preexistente para manter a restrição de identidade. Dessa maneira, a exclusividade dos objetos Symbol pode ser mantida na serialização.

Ankush soni
fonte
0

Como já foi respondido, readResolveé um método privado usado no ObjectInputStream ao desserializar um objeto. Isso é chamado antes do retorno da instância real. No caso de Singleton, aqui podemos forçar o retorno de referência de instância singleton já existente, em vez de referência de instância desserializada. Temos o mesmo writeReplacepara ObjectOutputStream.

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

}

}

Resultado:

Before serialization: com.ej.item3.SingletonWithSerializable@7852e922
After deserialization: com.ej.item3.SingletonWithSerializable@7852e922
Omkar
fonte