조슈아 블로크 님의 "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)
 
 
제한 사항
확장 가능한 클래스
- 직렬화된 상태가 상속 계층을 통해 전달되지 못함
 
순환 참조