IT박스

자바 직렬화 : readObject () vs. readResolve ()

itboxs 2020. 7. 7. 07:56
반응형

자바 직렬화 : readObject () vs. readResolve ()


Effective Java 및 기타 소스 은 직렬화 가능 Java 클래스로 작업 할 때 readObject () 메소드를 사용하는 방법과시기에 대한 좋은 설명을 제공합니다. 반면에 readResolve () 메소드는 약간의 수수께끼로 남아 있습니다. 기본적으로 내가 찾은 모든 문서는 둘 중 하나만 언급하거나 둘 다 개별적으로 언급했습니다.

답변되지 않은 질문은 다음과 같습니다.

  • 두 방법의 차이점은 무엇입니까?
  • 언제 어떤 방법을 구현해야합니까?
  • 특히 무엇을 반환한다는 점에서 readResolve ()를 어떻게 사용해야합니까?

이 문제에 대해 약간의 지식을 밝힐 수 있기를 바랍니다.


readResolve스트림에서 읽은 객체 바꾸는 데 사용됩니다 . 제가 지금까지 본 유일한 용도는 싱글 톤을 적용하는 것입니다. 객체를 읽으면 싱글 톤 인스턴스로 교체하십시오. 이렇게하면 아무도 싱글 톤을 직렬화하고 역 직렬화하여 다른 인스턴스를 만들 수 없습니다.


readResolve메소드는 ObjectInputStream스트림에서 오브젝트를 읽고 호출자에게 리턴 할 준비가되었을 때 호출됩니다. ObjectInputStream객체의 클래스가 readResolve메소드를 정의하는지 여부를 확인합니다 . 메소드가 정의 된 경우, readResolve스트림의 오브젝트가 리턴 될 오브젝트를 지정할 수 있도록 메소드가 호출됩니다. 반환 된 객체는 모든 용도와 호환되는 유형이어야합니다. 호환되지 않으면 ClassCastException유형 불일치가 발견 될 때 a 가 발생합니다.


항목 90, 유효 Java, 3 차 Ed readResolvewriteReplace직렬 프록시-주요 용도. 예제는 기본 직렬화를 사용하여 필드를 읽고 쓰므로 쓰기 readObjectwriteObject메소드를 작성하지 않습니다 .

readResolvereadObject반환 된 후 호출됩니다 (반대로 writeReplacewriteObject다른 객체에서 호출 됨). 메소드가 돌려주는 this객체는, 유저에게 돌려 주어진 객체 ObjectInputStream.readObject와, 스트림 내의 객체에 대한 추가의 참조를 치환 합니다 . 모두 readResolvewriteReplace동일하거나 서로 다른 유형의 객체를 반환 할 수 있습니다. 동일한 유형을 반환하면 필드가 있어야 final하고 이전 버전과의 호환성이 필요하거나 값을 복사 및 / 또는 검증 해야하는 경우에 유용 합니다.

readResolve싱글 톤 속성을 사용 하지 않습니다.


readResolve를 사용하여 readObject 메소드를 통해 직렬화 된 데이터를 변경할 수 있습니다. 예를 들어 xstream API는이 기능을 사용하여 직렬화 해제 할 XML에없는 일부 속성을 초기화합니다.

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


readResolve는 기존 객체를 반환해야하는 경우를위한 것입니다. 예를 들어 병합해야 할 중복 입력을 확인 중이거나 (예 : 일관된 분산 시스템에서) 인식하기 전에 도착할 수있는 업데이트이므로 이전 버전


readResolve ()는 직렬화 중에 싱글 톤 계약을 보장합니다.
제발 참조


readObject ()는 ObjectInputStream 클래스 의 기존 메소드 입니다. deserialization시 객체를 읽는 동안 readObject 메소드는 내부적으로 readResolve 메소드를 사용하여 deserialize되는 클래스 객체를 확인합니다. readResolve 메소드가 존재하면 readResolve 메소드를 호출하고 예.

따라서 readResolve 메소드를 작성하는 것은 직렬화 / 역 직렬화를 통해 다른 인스턴스를 얻을 수없는 순수한 싱글 톤 디자인 패턴을 달성하는 좋은 방법입니다.


직렬화를 사용하여 객체를 파일로 저장할 수 있도록 변환하면 readResolve () 메소드를 트리거 할 수 있습니다. 이 메소드는 개인용이며 직렬화 해제 중에 오브젝트를 검색하는 동일한 클래스에 유지됩니다. 역 직렬화 후에 반환되는 객체가 직렬화 된 것과 동일하게됩니다. 그건,instanceSer.hashCode() == instanceDeSer.hashCode()

readResolve () 메소드는 정적 메소드가 아닙니다. in.readObject()역 직렬화하는 동안 호출 된 후에 는 반환 된 객체가 다음과 같이 직렬화 된 객체와 동일한 지 확인합니다.out.writeObject(instanceSer)

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

이런 식 으로 동일한 인스턴스가 반환 될 때마다 싱글 톤 디자인 패턴 구현에 도움이됩니다 .

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

readResolve 메소드

직렬화 가능 클래스와 외부화 가능 클래스의 경우, readResolve 메소드는 클래스가 호출자에게 리턴되기 전에 스트림에서 읽은 오브젝트를 대체 / 해결할 수있게합니다. readResolve 메소드를 구현함으로써 클래스는 직렬화 해제되는 자체 인스턴스의 유형과 인스턴스를 직접 제어 할 수 있습니다. 방법은 다음과 같이 정의됩니다.

모든 액세스 수정 자 객체 readResolve ()는 ObjectStreamException을 던집니다.

readResolve에의 할 때 방법이라고 하는 ObjectInputStream가 스트림으로부터 객체를 판독하고 호출자에게 반환하도록 준비된다. ObjectInputStream 은 객체의 클래스가 readResolve 메소드를 정의하는지 확인합니다. 메소드가 정의 된 경우 readResolve 메소드가 호출되어 스트림의 오브젝트가 리턴 될 오브젝트를 지정할 수 있도록합니다. 반환 된 객체는 모든 용도와 호환되는 유형이어야합니다. 호환되지 않으면 형식 불일치가 발견 될 때 ClassCastException 이 발생합니다.

예를 들어 각 심볼 바인딩의 단일 인스턴스 만 가상 머신 내에 존재 하는 Symbol 클래스를 작성할 수 있습니다. 하는 readResolve의 방법은 그 기호가 이미 정의되어 있는지 확인하고 신원 제약 조건을 유지하기 위해 기존의 해당 기호 객체를 대체하도록 구현 될 것이다. 이러한 방식으로 직렬화에서 Symbol 개체의 고유성을 유지할 수 있습니다.


이미 대답했듯이 readResolve객체를 직렬화 해제하는 동안 ObjectInputStream에서 사용되는 전용 메소드입니다. 실제 인스턴스가 리턴되기 직전에 호출됩니다. 싱글 톤의 경우, 직렬화 해제 된 인스턴스 참조 대신 기존의 싱글 톤 인스턴스 참조를 강제로 반환 할 수 있습니다. 마찬가지로 writeReplaceObjectOutputStream도 있습니다.

Example for 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);
    }

}

}

Output:

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

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.

참고URL : https://stackoverflow.com/questions/1168348/java-serialization-readobject-vs-readresolve

반응형