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


127

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

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

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

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


오라클 JDK의 예 :String.CaseInsensitiveComparator.readResolve()
kevinarpe

답변:


138

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


3
악성 코드 (또는 데이터)가이를 해결할 수있는 여러 가지 방법이 있습니다.
Tom Hawtin-tackline

6
Josh Bloch는 이것이 효과적인 Java 2nd ed에서 이것이 깨지는 조건에 대해 이야기합니다. 항목 그는 그가 다시 년의 구글 IO 커플 (이야기의 끝으로 몇 번)에 준이 이야기에서 이것에 대해 언급 77 : youtube.com/watch?v=pi_I7oD_uGI
calvinkrishy

17
이 답변은 transient필드를 언급하지 않으므로 약간 부적절 합니다. 읽은 후 오브젝트 readResolve해결 하는 데 사용됩니다 . 사용 예는 아마도 개체가 기존 데이터에서 다시 생성 할 수 있고 직렬화 할 필요가없는 캐시를 보유하고 있다는 것입니다. 캐시 된 데이터를 선언 할 수 transientreadResolve()역 직렬화 후를 재 구축 할 수 있습니다. 그런 것들이이 방법의 목적입니다.
Jason C

2
@JasonC 귀하의 의견은 "[일시적인 처리]와 같은 것이이 방법의 목적 "이라고 오해의 소지가 있습니다. Java 문서를 참조하십시오. Serializable" 스트림에서 인스턴스를 읽을 때 대체 를 지정해야하는 클래스는 이 [ readResolve] 특수 메소드를 구현해야합니다 ..."라고 말합니다.
Opher

2
readResolve 메소드는 많은 객체를 직렬화하여 데이터베이스에 저장했다고 가정하는 경우에도 사용할 수 있습니다. 나중에 해당 데이터를 새 형식으로 마이그레이션하려는 경우 readResolve 메소드에서 쉽게 얻을 수 있습니다.
나일 쉬 라 자니

29

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

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

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


9

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

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


1
XML과 Xstream은 Java Serialization에 대한 질문과 관련이 없으며 몇 년 전에 올바르게 대답했습니다. -1
Lorne의 후작

5
허용되는 대답은 readResolve가 객체를 바꾸는 데 사용된다는 것을 나타냅니다. 이 답변은 역 직렬화 중에 객체를 수정하는 데 사용할 수있는 유용한 추가 정보를 제공합니다. XStream은 가능한 유일한 라이브러리가 아닌 예제로 제공되었습니다.
Enwired

5

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


가 readResolve ()는 나에게 분명하지만 여전히 내가 마음에 몇 가지 설명 할 수없는 질문이 있지만, 당신의 대답은 내 마음, 감사 읽기
Rajni Gangwar

5

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

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



2

직렬화를 사용하여 객체를 파일로 저장할 수 있도록 변환하면 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 
}

1

나는이 질문이 실제로 오래되었고 받아 들여진 대답을 알고 있지만 Google 검색에서 매우 높게 나타나기 때문에 중요하다고 생각되는 세 가지 경우에 대한 답변이 없기 때문에 무게가 달릴 것이라고 생각했습니다. 행동 양식. 물론 모든 사용자 지정 직렬화 형식이 실제로 필요하다고 가정합니다.

컬렉션 클래스를 예로 들어 보겠습니다. 링크 된 목록 또는 BST의 기본 직렬화는 요소를 순서대로 직렬화하는 것과 비교하여 성능이 거의 떨어지지 않으면 서 공간이 크게 손실됩니다. 컬렉션이 프로젝션 또는 뷰인 경우 공개 API에서 제공하는 것보다 더 큰 구조에 대한 참조를 유지합니다.

  1. 직렬화 된 객체에 사용자 정의 직렬화가 필요한 불변 필드 writeObject/readObject가있는 경우, 직렬화 된 객체가 작성된 스트림 부분을 읽기 전에 생성되므로 원래 솔루션 은 충분하지 않습니다 writeObject. 다음과 같이 링크 된 목록을 최소한으로 구현하십시오.

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

이 구조는 head모든 링크 의 필드를 재귀 적으로 쓰고 그 뒤에 null값 을 붙여서 직렬화 할 수 있습니다 . 그러나 이러한 형식을 역 직렬화하는 것은 불가능합니다. readObject멤버 필드의 값을 변경할 수 없습니다 (이제 고정됨 null). 여기에 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;
        }
    }
}

위의 예제가 컴파일되지 않거나 작동하지 않으면 죄송하지만 내 요점을 설명하기에 충분합니다. 이것이 아주 많이 가져온 예라고 생각한다면 많은 기능적 언어가 JVM에서 실행되며 이러한 접근 방식은 필수적입니다.

  1. 실제로 우리에게 쓴 것과 다른 클래스의 객체를 역 직렬화하려고 할 수 있습니다 ObjectOutputStream. 이것은 java.util.List슬라이스를 더 길게 노출시키는 목록 구현 과 같은 뷰의 경우입니다 ArrayList. 분명히, 전체 백업 목록을 직렬화하는 것은 좋지 않은 생각이며, 본 슬라이스에서 요소 만 작성해야합니다. 그러나 왜 멈추고 역 직렬화 후에 쓸모없는 수준의 간접 성이 있습니까? 우리는 단순히 스트림에서 요소를 읽고 ArrayList뷰 클래스에 래핑하는 대신 직접 반환 할 수 있습니다.

  2. 대안 적으로, 직렬화 전용의 유사한 델리게이트 클래스를 갖는 것이 설계 선택 일 수있다. 좋은 예는 직렬화 코드를 재사용하는 것입니다. 예를 들어, StringBuilder for String과 유사한 빌더 클래스가있는 경우 빈 빌더를 스트림에 작성하고 컬렉션의 반복자에서 리턴 한 콜렉션 크기 및 요소를 사용하여 콜렉션을 직렬화하는 직렬화 대리자를 작성할 수 있습니다. 역 직렬화는 빌더를 읽고, 이후에 읽은 모든 요소를 ​​추가하고 build(), 델리게이트에서 final의 결과를 리턴하는 작업 readResolve입니다. 이 경우 컬렉션 계층의 루트 클래스에서만 직렬화를 구현해야하며, 현재 또는 향후 구현에서 추가 코드가 필요하지 않습니다.iterator()builder()메소드 (동일한 유형의 콜렉션을 재생성하는 후자-자체적으로 매우 유용한 기능). 또 다른 예는 우리가 완전히 제어하지 않는 코드를 가진 클래스 계층 구조를 갖는 것입니다. 타사 라이브러리의 기본 클래스에는 우리가 알지 못하는 여러 개인 필드가있을 수 있으며 버전에 따라 변경 될 수 있습니다. 직렬화 된 객체. 이 경우 역 직렬화시 데이터를 작성하고 수동으로 개체를 다시 작성하는 것이 더 안전합니다.


0

readResolve 메소드

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

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

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

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


0

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

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

}

}

산출:

Before serialization: com.ej.item3.SingletonWithSerializable@7852e922
After deserialization: com.ej.item3.SingletonWithSerializable@7852e922
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.