요약
•
Serializable 구현은 신중하게 결정하자.
Serializable 구현은 신중하게 결정하자
•
Serializable을 구현하면 해당 클래스는 공개 API가 된다.
◦
Serializable을 구현하면 직렬화된 바이트 스트림 인코딩 자체가 하나의 공개 API가 되어, 릴리스한 뒤에는 해당 직렬화 형태를 계속해서 지원해야하기 때문에 수정하기 어렵다.
◦
또한 Serializable을 구현하면 private나 package-private로 구현된 내부 인스턴스 필드도 공개 API가 되어 캡슐화가 깨지게된다.
◦
이런 이유들로 직렬화 가능한 클래스를 만들고자 한다면, 길게 보고 감당할 수 있을만큼 고품질의 직렬화 형태로 주의해서 설계해야 한다.
•
직렬화를 구현하려면 직렬화 버전(SerialVersionUID)를 직접 관리해야 한다.
◦
SUID를 명시하지 않으면 런타임에 해시함수를 통해 자동으로 생성되어 클래스에 들어가는데, 클래스가 수정되면 해시코드 값이 달라져 호환이 되지 않는다.
◦
직렬화를 구현하기 위해서는 자동 생성되는 SUID에 의존하면 안되고, 직접 버전 관리를 해주어야 한다.
•
Serializable을 구현하면 버그와 보안 구멍이 생길 위험이 높아진다.
◦
직렬화는 언어 기본 매커니즘인 생성자로 객체를 만드는 것을 우회하여 객체를 생성할 수 있다.
◦
이 때문에 역직렬화를 사용하면 불변식이 깨지거나 허가되지 않은 접근을 쉽게 노출 시킬 수 있다.
•
Serializable을 구현하면 테스트할 것이 늘어난다.
◦
직렬화 가능 클래스가 수정되면 신버전의 인스턴스를 직렬화 후 구버전에서 역직렬화가 되는지와 구버전에서 직렬화 후 신버전에서 역직렬화가 되는지 테스트 해야한다.
•
Serializable을 구현하는 비용과 이득을 잘 계산해야한다.
◦
위의 문제점들을 감안하고 Serializable을 구현하는 비용이 적지 않으니, 직렬화를 구현하기 위한 이득과 비용을 잘 저울질 해야한다.
◦
역사적으로 BigInteger나 String의 값 클래스와 컬렉션 클래스는 Serializable을 구현했고, 스레드 풀처럼 동작하는 객체를 표현하는 클래스는 대부분 구현하지 않았다.
•
상속용으로 설계된 클래스나 인터페이스는 Serializable을 구현하면 안된다.
◦
Serializable을 구현한 클래스나 인터페이스를 상속받으면, 위의 직렬화에 대한 부담을 그대로 이어받아 직렬화에 대한 설계를 해야한다.
◦
Serializable을 구현한 클래스만 지원하는 프레임워크를 사용하는 상황이 아니라면 상속용 클래스나 인터페이스에는 Serializable을 구현하지말자.
◦
그래도 상속용으로 구현하려면 불변식을 보장해야하는 인스턴스 필드나 메서드가 있을 때 finalize 메서드를 final을 붙여, 재정의하여 하위 클래스에서 finalize 메서드를 재정의해 사용하는 것을 막아야 finalizer 공격을 막을 수 있다.
◦
상속 받은 클래스가 Serializable을 지원하지 않는 경우, 하위 클래스에서 직렬화를 구현하려면 기본 생성자를 제공하거나 직렬화 프록시 패턴을 사용해야 한다.
•
내부 클래스는 직렬화를 구현하지 말아야 한다.
◦
내부 클래스의 경우 기본 직렬화 형태가 불분명하기 때문에 직렬화를 하면 안된다.
◦
다만 static 멤버 클래스는 직렬화를 구현해도 상관없다.