Java/EffectiveJava

Effective Java Item86. Serializable을 구현할지는 신중히 결정하라.

Flambee 2025. 3. 23. 22:15

직렬화는 쉽다. implements Serializable만 붙이면 된다.

Serializable을 구현하면 릴리스한 뒤에는 수정하기 어렵다. 클래스가 Serializable을 구현하면 직렬화된 바이트 스트림 인코딩도 하나의 공개 API가 된다. 이 클래스가 널리 퍼진다면 그 직렬화 형태도 영원히 지원해야한다.

기본 직렬화 형태에서는 클래스의 private과 package-private 인스턴스 필드들마저 API로 공개되는 꼴이 된다.(캡슐화가 깨진다). 필드로의 접근을 최대한 막아 정보를 은닉하라는 조언도 무력화된다.

직렬화 가능 클래스를 만들고자 하면, 길게 보고 감당할 수 있을 만큼 고품질의 직렬화 형태도 주의해서 함께 설계해야 한다.

직렬화 설계 시 주의 사항

  • 스트림 고유 식별자, serialVersionUID.
    • 모든 직렬화된 클래스는 고유 식별번호를 부여 받는다.
    • 이 번호를 명시하지 않으면 시스템이 런타임에 암호해시 함수를 적용해 자동으로 클래스 안에 생성한다.
    • 클래스 이름, 구현한 인터페이스들, 컴파일러가 자동으로 생성해 넣은 것을 포함한 대부분의 클래스 멤버들이 고려된다.
    • 자동 생성되는 값에 의존하면 쉽게 호환성이 깨져버려 런타임에 InvalidClassException이 발생한다.
  • 버그와 보안 구멍이 생길 위험이 높아진다.
    • 객체 생성자를 사용해 만드는게 기본이다. 즉, 직렬화는 언어의 기본 메카니즘을 우회하는 객체 생성 기법이다.
    • 생성자에서 구축한 불변식을 모두 보장해야 하고 생성 도중 공격자가 객체 내부를 들여다 볼 수 없도록 해야한다.
  • 해당 클래스의 신버전을 릴리스할 때 테스트할 것이 늘어난다는 점이다.
    • 신 버전 인스턴스를 직렬화한 후 구버전으로 역직렬화할 수 있는지, 그리고 그 반대도 가능한지를 검사해야 한다.
    • 양방향 직렬화/역직렬화가 모두 성공하고 원래의 객체를 충실히 복제해내는지 반드시 확인해야 한다.
  • 구현 여부는 가볍게 결정할 사안이 아니다.
  • 상속용으로 설계된 클래스는 대부분 Serializable을 구현하면 안ㄷ 되며, 인터페이스도 대부분 Serializable을 확장해서는 안 된다.
  • 내부 클래스는 직렬화를 구현하지 말아야 한다.
    • 내부 클래스에는 바깥 인스턴스의 참조와 유효 범위 안의 지역변수 값들을 저장하기 위해 컴파일러가 생성한 필드들이 자동으로 추가된다. 즉, 기본 직렬화 형태는 분명하지가 않다.
    • 단, 정적 멤버 클래스는 Serializable을 구현해도 된다.

Serializable을 구현하지 않기로 할 때는 한 가지만 주의하면 된다. 상속용 클래스인데 직렬화를 지원하지 않으면 그 하위 클래스에서 직렬화를 지원하려 할 때 부담이 늘어난다. 이 때 상위 클래스는 매개변수가 없는 생성자를 제공해야 하는데, 이때는 직렬화 프록시 패턴을 사용해야 한다.