Java

[Effective Java] 12-2. 직렬화

noahkim_ 2025. 1. 3. 04:49

조슈아 블로크 님의 "Effective Java" 책을 정리한 포스팅 입니다.

 

4. readObject 메서드는 방어적으로 작성하라

readObject()는 또다른 public 생성자

  • 직렬화된 객체의 상태를 복원하는 메서드
  • 복원 중에 새로운 객체를 생성하는 역할을 함

 

매개변수로 바이트 스트림을 받는 생성자

  • 바이트 스트림을 받아서 객체 상태를 읽고 복원하는 방식
  • 바이트 스트림이 유효한지 먼저 검사해야 함

 

인수가 유효한지 검사

  • defaultReadObject를 호출하여 기본 상태 복원 및 유효성 검사

 

매개변수를 방어적으로 복사해야 함

  • 클라이언트가 소유해서는 안되는 객체 참조를 갖는 필드를 모두 반드시 방어적으로 복사해야 함
  • final은 방어적 복사 불가

 

clone()은 어떻게 복사될지 모르므로 사용하지 않도록

  • 객체가 어떻게 복사될 지 예측하기 어려움
  • 명시적으로 깊은 복사를 구현하거나 새로운 객체를 생성하는 방식으로 안전하게 복사해야 함

 

직렬화 프록시 사용하기

 

5. 인스턴스 수를 통제해야 한다면 readResolve보다는 열거 타입을 사용하라

readResolve()

public class Singleton implements Serializable {
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {
        // private constructor to prevent instantiation
    }

    // Singleton instance retrieval method
    public static Singleton getInstance() {
        return INSTANCE;
    }

    // readResolve() method to ensure singleton behavior during deserialization
    private Object readResolve() throws ObjectStreamException {        
        return INSTANCE;
    }
}
  • readObject가 만들어낸 인스턴스를 다른 것으로 대체할 수 있음
  • 직렬화된 객체가 역직렬화될 때 ObjectInputStream에 의해 호출됨

 

접근성이 매우 중요
  • private: 외부에서 호출되지 않도록 해야 함

 

6. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라

직렬화 프록시

  • 직렬화된 객체 대신 직렬화 가능한 중첩 클래스를 사용하여 객체를 더 안전하게 직렬화하는 기법
  • 위험성을 줄이고, 불필요한 접근이나 공개를 차단

 

중첩 클래스

public class OuterClass implements Serializable {
    private static final long serialVersionUID = 1L;
    private String outerState;

    public OuterClass(String outerState) {
        this.outerState = outerState;
    }

    public String getOuterState() {
        return outerState;
    }

    // Serialization Proxy
    private static class SerializationProxy implements Serializable {
        private static final long serialVersionUID = 1L;
        private String outerState;

        // 프록시 생성자: OuterClass의 상태를 받아서 저장
        public SerializationProxy(OuterClass outerClass) {
            this.outerState = outerClass.outerState;
        }

        // 역직렬화 후 실제 객체를 생성
        private Object readResolve() {
            return new OuterClass(outerState);
        }
    }

    // 직렬화된 객체 대신 SerializationProxy 사용
    private Object writeReplace() {
        return new SerializationProxy(this);
    }
}
  • 중첩 클래스를 통해 바깥 클래스의 직렬화된 상태를 안전하게 관리
  • 바깥 클래스와 중첩 클래스 모두 Serializable 구현
  • 중첩 클래스를 private static으로 선언

 

생성자
  • 오직 하나
  • 바깥 클래스를 매개변수로 받기

 

장점
  • 객체 상태 캡슐화
  • 호환성
  • 보안성
    • 내부 필드 보호
    • 가짜 바이트 스트림 공격과 내부 필드 탈취 공격을 차단해줌
  • 불변성 유지 
    • 역직렬화한 인스턴스와 원래의 직렬화된 인스턴스의 클래스가 달라도 정상 동작 (EnumSet)

 

제한 사항

확장 가능한 클래스
  • 직렬화된 상태가 상속 계층을 통해 전달되지 못함

 

순환 참조

 

'Java' 카테고리의 다른 글

[Effective Java] 12-1. 직렬화  (0) 2025.01.03
[Effective Java] 11-2. 동기화  (2) 2025.01.03
[Effective Java] 11-1. 동기화  (0) 2025.01.01
[Effective Java] 10-2. 예외  (0) 2025.01.01
[Effective Java] 10-1. 예외  (0) 2025.01.01