테스트 코드를 통해 Java의 Serializable 인터페이스가 어떻게 동작하는지 직접 확인해보겠습니다.
Java의 직렬화(Serialization)는 객체를 바이트 스트림으로 변환하여 파일이나 네트워크로 전송할 수 있게 해주는 기능입니다. 이 기능이 어떻게 동작하고 언제 실패하는지 실제 테스트 코드를 통해 알아보겠습니다.
테스트 환경:
먼저 Serializable을 올바르게 구현한 클래스가 정상적으로 직렬화/역직렬화되는지 확인해보겠습니다.
@Test
void serializableClassShouldBeSerializedAndDeserialized() throws Exception {
// Given: Serializable을 구현한 객체 생성
SerializablePerson person = new SerializablePerson("홍길동", 20);
// When: 객체를 직렬화
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(person);
oos.close();
// And: 직렬화된 데이터를 다시 역직렬화
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
SerializablePerson result = (SerializablePerson) ois.readObject();
ois.close();
// Then: 원본 데이터가 정확히 복원되어야 함
assertThat(result.name).isEqualTo("홍길동");
assertThat(result.age).isEqualTo(20);
}
✅ 결과: Serializable을 구현한 객체는 정상적으로 직렬화/역직렬화되며, 모든 필드 값이 정확히 복원됩니다.
Serializable 인터페이스를 구현하지 않은 클래스를 직렬화하면 어떻게 될까요?
@Test
void nonSerializableClassShouldThrowException() {
// Given: Serializable을 구현하지 않은 객체
NonSerializablePerson person = new NonSerializablePerson("임꺽정", 30);
// When & Then: 직렬화 시도 시 NotSerializableException 발생
assertThatThrownBy(() -> {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(person); // 여기서 예외 발생!
}).isInstanceOf(NotSerializableException.class);
}
❌ 결과: NotSerializableException이 발생하며 직렬화에 실패합니다. Java는 안전성을 위해 명시적으로 Serializable을 구현한 클래스만 직렬화를 허용합니다.
Serializable을 구현했더라도, 직렬화할 수 없는 필드가 포함되어 있으면 어떻게 될까요?
@Test
void nonSerializableClassWithNonSerializableFieldsShouldThrowException() {
// Given: Serializable을 구현했지만 Thread 필드를 가진 객체
NonSerializablePerson2 person = new NonSerializablePerson2("이몽룡", 25, Thread.currentThread());
// When & Then: Thread는 직렬화 불가능하므로 예외 발생
assertThatThrownBy(() -> {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(person); // Thread 필드 때문에 실패
}).isInstanceOf(NotSerializableException.class);
}
❌ 결과: Thread, Socket, InputStream 등은 직렬화할 수 없는 객체입니다. 이런 필드가 있으면 전체 객체의 직렬화가 실패합니다.
💡 해결책: transient 키워드를 사용하여 해당 필드를 직렬화에서 제외할 수 있습니다.
serialVersionUID가 다를 때 역직렬화가 어떻게 실패하는지 확인해보겠습니다.
@Test
void serialVersionUIDMismatchShouldThrowException() throws Exception {
// Given: 원본 객체를 직렬화
SerializablePerson person = new SerializablePerson("홍길동", 20);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(person);
oos.close();
byte[] serializedData = baos.toByteArray();
// When: serialVersionUID가 다른 클래스로 역직렬화 시도
class ModifiedSerializablePerson implements Serializable {
private static final long serialVersionUID = 2L; // 다른 UID!
String name;
int age;
ModifiedSerializablePerson(String name, int age) {
this.name = name;
this.age = age;
}
}
// Then: InvalidClassException 발생
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serializedData));
assertThatThrownBy(ois::readObject)
.isInstanceOf(java.io.InvalidClassException.class);
ois.close();
}
❌ 결과: InvalidClassException이 발생합니다. serialVersionUID는 클래스의 버전을 식별하는 ID로, 다르면 역직렬화를 거부합니다.
NotSerializableExceptionNotSerializableException InvalidClassExceptiontransient 키워드 사용