Java serialization: readObject() vs. readResolve()

前端 未结 10 937
花落未央
花落未央 2020-12-02 04:07

The book Effective Java and other sources provide a pretty good explanation on how and when to use the readObject() method when working with serializable Java class

相关标签:
10条回答
  • 2020-12-02 04:24

    When serialization is used to convert an object so that it can be saved in file, we can trigger a method, readResolve(). The method is private and is kept in the same class whose object is being retrieved while deserialization. It ensures that after the deserialization, what object is returned is the same as was serialised. That is, instanceSer.hashCode() == instanceDeSer.hashCode()

    readResolve() method is not a static method. After in.readObject() is called while deserialisation it just makes sure that the returned object is the same as the one which was serialized as below while out.writeObject(instanceSer)

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

    In this way, it also helps in singleton design pattern implementation, because every time same instance is returned.

    public static ABCSingleton getInstance(){
        return ABCSingleton.instance; //instance is static 
    }
    
    0 讨论(0)
  • 2020-12-02 04:25

    Item 90, Effective Java, 3rd Ed covers readResolve and writeReplace for serial proxies - their main use. The examples do not write out readObject and writeObject methods because they are using default serialisation to read and write fields.

    readResolve is called after readObject has returned (conversely writeReplace is called before writeObject and probably on a different object). The object the method returns replaces this object returned to the user of ObjectInputStream.readObject and any further back references to the object in the stream. Both readResolve and writeReplace may return objects of the same or different types. Returning the same type is useful in some cases where fields must be final and either backward compatibility is required or values must copied and/or validated.

    Use of readResolve does not enforce the singleton property.

    0 讨论(0)
  • 2020-12-02 04:25

    readObject() is an existing method in ObjectInputStream class.while reading object at the time of deserialization readObject method internally check whether the class object which is being deserialized having readResolve method or not if readResolve method exist then it will invoke readResolve method and return the same instance.

    So the intent of writing readResolve method is a good practice to achieve pure singleton design pattern where no one can get another instance by serializing/deserializing.

    0 讨论(0)
  • 2020-12-02 04:31

    I know this question is really old and has an accepted answer, but as it pops up very high in google search I thought I'd weigh in because no provided answer covers the three cases I consider important - in my mind the primary use for these methods. Of course, all assume that there is actually a need for custom serialization format.

    Take, for example collection classes. Default serialization of a linked list or a BST would result in a huge loss of space with very little performance gain comparing to just serializing the elements in order. This is even more true if a collection is a projection or a view - keeps a reference to a larger structure than it exposes by its public API.

    1. If the serialized object has immutable fields which need custom serialization, original solution of writeObject/readObject is insufficient, as the deserialized object is created before reading the part of the stream written in writeObject. Take this minimal implementation of a linked list:

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

    This structure can be serialized by recursively writing the head field of every link, followed by a null value. Deserializing such a format becomes however impossible: readObject can't change the values of member fields (now fixed to null). Here come the writeReplace/readResolve pair:

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

    I am sorry if the above example doesn't compile (or work), but hopefully it is sufficient to illustrate my point. If you think this is a very far fetched example please remember that many functional languages run on the JVM and this approach becomes essential in their case.

    1. We may want to actually deserialize an object of a different class than we wrote to the ObjectOutputStream. This would be the case with views such as a java.util.List list implementation which exposes a slice from a longer ArrayList. Obviously, serializing the whole backing list is a bad idea and we should only write the elements from the viewed slice. Why stop at it however and have a useless level of indirection after deserialization? We could simply read the elements from the stream into an ArrayList and return it directly instead of wrapping it in our view class.

    2. Alternatively, having a similar delegate class dedicated to serialization may be a design choice. A good example would be reusing our serialization code. For example, if we have a builder class (similar to the StringBuilder for String), we can write a serialization delegate which serializes any collection by writing an empty builder to the stream, followed by collection size and elements returned by the colection's iterator. Deserialization would involve reading the builder, appending all subsequently read elements, and returning the result of final build() from the delegates readResolve. In that case we would need to implement the serialization only in the root class of the collection hierarchy, and no additional code would be needed from current or future implementations, provided they implement abstract iterator() and builder() method (the latter for recreating the collection of the same type - which would be a very useful feature in itself). Another example would be having a class hierarchy which code we don't fully control - our base class(es) from a third party library could have any number of private fields we know nothing about and which may change from one version to another, breaking our serialized objects. In that case it would be safer to write the data and rebuild the object manually on deserialization.

    0 讨论(0)
  • 2020-12-02 04:32

    The readResolve Method

    For Serializable and Externalizable classes, the readResolve method allows a class to replace/resolve the object read from the stream before it is returned to the caller. By implementing the readResolve method, a class can directly control the types and instances of its own instances being deserialized. The method is defined as follows:

    ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

    The readResolve method is called when ObjectInputStream has read an object from the stream and is preparing to return it to the caller. ObjectInputStream checks whether the class of the object defines the readResolve method. If the method is defined, the readResolve method is called to allow the object in the stream to designate the object to be returned. The object returned should be of a type that is compatible with all uses. If it is not compatible, a ClassCastException will be thrown when the type mismatch is discovered.

    For example, a Symbol class could be created for which only a single instance of each symbol binding existed within a virtual machine. The readResolve method would be implemented to determine if that symbol was already defined and substitute the preexisting equivalent Symbol object to maintain the identity constraint. In this way the uniqueness of Symbol objects can be maintained across serialization.

    0 讨论(0)
  • 2020-12-02 04:38

    readResolve is for when you may need to return an existing object, e.g. because you're checking for duplicate inputs that should be merged, or (e.g. in eventually-consistent distributed systems) because it's an update that may arrive before you're aware of any older versions.

    0 讨论(0)
提交回复
热议问题