Java - Serializable로 떠나는 객체의 여행

꾸준히·2025년 8월 2일

Java

목록 보기
1/2
post-thumbnail

목차

  1. 개요
  2. 주요 특징
  3. serialVersionUID란?
    • 실제 serialVersionUID 예시
    • serialVersionUID 자동 생성으로 인한 역직렬화 실패 예시
  4. 마커 인터페이스 패턴(Marker Interface Pattern)
    • Serializable의 필요성
  5. 예시 코드
    • 직렬화/역직렬화 예시
    • Serializable이 불가능한 예시
  6. 참고

1. 개요

Serializable 인터페이스는 자바에서 객체를 직렬화(Serialization)할 수 있도록 해주는 마커 인터페이스입니다. 직렬화란 객체의 상태를 바이트 스트림으로 변환하여
파일, 네트워크 등으로 전송하거나 저장할 수 있게 하는 기능입니다.

  • 패키지: java.io.Serializable
  • 메서드가 없는 마커 인터페이스입니다.
  • 역직렬화(Deserialization)를 통해 바이트 스트림을 다시 객체로 복원할 수 있습니다.
  • 직렬화 대상 클래스는 반드시 Serializable을 구현해야 하며, 그렇지 않으면 NotSerializableException이 발생합니다.

2. 주요 특징

  • transient 키워드를 사용하면 해당 필드는 직렬화 대상에서 제외됩니다.
  • static 필드는 직렬화되지 않습니다.

3. serialVersionUID란?

  • serialVersionUID는 직렬화된 객체와 클래스의 버전 일치 여부를 확인하는 고유 식별자입니다.
  • serialVersionUID를 명시하지 않으면, 자바가 자동으로 생성하지만 클래스 구조가 바뀌면 값이 달라져 역직렬화에 실패할 수 있습니다.
  • 명시적으로 선언하면 클래스 구조가 일부 변경되어도 호환성을 유지할 수 있습니다.
  • serialVersionUID가 다르면 역직렬화 시 InvalidClassException이 발생합니다.

실제 serialVersionUID 예시

  • java.lang.String: private static final long serialVersionUID = -6849794470754667710L;
  • java.util.ArrayList: private static final long serialVersionUID = 8683452581122892189L;
  • java.util.HashMap: private static final long serialVersionUID = 362498820763181265L;
  • java.lang.Integer: private static final long serialVersionUID = 1360826667806852920L;
  • java.util.Date: private static final long serialVersionUID = 7523967970034938905L;

이처럼 JDK의 주요 표준 클래스들도 serialVersionUID를 명시적으로 선언하여, 버전 호환성과 직렬화 안정성을 보장하고 있습니다.

serialVersionUID 자동 생성으로 인한 역직렬화 실패 예시

serialVersionUID를 명시하지 않으면, 자바는 클래스 구조(필드, 메서드 등)를 바탕으로 자동으로 serialVersionUID를 생성합니다. 이때 클래스 구조가
변경되면 serialVersionUID 값도 달라져, 이전에 직렬화한 객체를 역직렬화할 때 InvalidClassException이 발생할 수 있습니다.

예시 코드

  1. 초기 클래스 정의 및 직렬화
// 버전 1
public class AutoUIDPerson implements Serializable {

  private String name;

  public AutoUIDPerson(String name) {
    this.name = name;
  }
}

// 객체 직렬화
AutoUIDPerson person = new AutoUIDPerson("홍길동");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"));
oos.

writeObject(person);
oos.

close();
  1. 클래스 구조 변경 후 역직렬화 시도
// 버전 2 (필드 추가)
public class AutoUIDPerson implements Serializable {

  private String name;
  private int age; // 필드 추가

  public AutoUIDPerson(String name, int age) {
    this.name = name;
    this.age = age;
  }
}

// 이전에 저장한 파일 역직렬화
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"));
AutoUIDPerson person = (AutoUIDPerson) ois.readObject(); // InvalidClassException 발생
ois.

close();

이처럼 serialVersionUID를 명시하지 않으면, 클래스 구조가 변경될 때마다 자동 생성된 값이 달라져 역직렬화에 실패할 수 있습니다. 따라서 버전 호환성을 위해
serialVersionUID를 명시적으로 선언하는 것이 안전합니다.

4. 마커 인터페이스 패턴(Marker Interface Pattern)

마커 인터페이스 패턴은 별도의 메서드 없이, 특정 인터페이스를 구현했다는 사실만으로 객체에 특별한 의미나 속성을 부여하는 디자인 패턴입니다. Serializable,
Cloneable, Remote 등이 대표적인 예입니다.

Serializable의 필요성

private void checkSerializable(Object object) throws NotSerializableException {
  if (object instanceof Serializable) {
    // 직렬화 OK!
  } else {
    throw new NotSerializableException();
  }
}
  • 자바에서 객체를 파일, 네트워크 등으로 저장하거나 전송하려면 객체를 바이트 스트림으로 변환(직렬화)해야 합니다.
  • 이때, 해당 객체가 Serializable 인터페이스를 구현하고 있어야만 자바 런타임이 안전하게 직렬화/역직렬화 작업을 수행할 수 있습니다.
  • 즉, Serializable은 "이 객체는 직렬화가 가능하다"는 신호를 자바 시스템에 전달하는 역할을 하며, 마커 인터페이스 패턴의 대표적인 사례입니다.

5. 예시 코드

직렬화/역직렬화 예시

아래는 Person 객체를 파일에 직렬화(저장)하고, 다시 역직렬화(복원)하는 예시입니다.

import java.io.*;

public class SerializationExample {

  public static void main(String[] args) {
    Person person = new Person("홍길동", 30, "secret");
    String filename = "person.ser";

    // 객체 직렬화
    try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {
      oos.writeObject(person);
      System.out.println("직렬화 완료: " + person);
    } catch (IOException e) {
      e.printStackTrace();
    }

    // 객체 역직렬화
    try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) {
      Person deserialized = (Person) ois.readObject();
      System.out.println("역직렬화 결과: " + deserialized);
    } catch (IOException | ClassNotFoundException e) {
      e.printStackTrace();
    }
  }
}

실행 결과 예시

직렬화 완료: Person{name='홍길동', age=30, password='secret'}
역직렬화 결과: Person{name='홍길동', age=30, password='null'}
  • password 필드는 transient로 선언되어 직렬화/역직렬화 과정에서 값이 저장되지 않습니다.
  • 이처럼 직렬화/역직렬화를 통해 객체를 파일, 네트워크 등으로 저장/전송하고 다시 복원할 수 있습니다.

예시 코드: Serializable 클래스

import java.io.Serializable;

public class Person implements Serializable {

  private static final long serialVersionUID = 1L;
  private String name;
  private int age;
  private transient String password; // 직렬화 제외

  public Person(String name, int age, String password) {
    this.name = name;
    this.age = age;
    this.password = password;
  }

  @Override
  public String toString() {
    return "Person{" +
        "name='" + name + '\'' +
        ", age=" + age +
        ", password='" + password + '\'' +
        '}';
  }
}

Serializable이 불가능한 예시

아래와 같이 Serializable 인터페이스를 구현하지 않은 클래스는 직렬화가 불가능하며, 시도 시 NotSerializableException이 발생합니다.

public class NotSerializablePerson {

  private String name;
  private int age;
  private Thread thread;

  public NotSerializablePerson(String name, int age, Thread thread) {
    this.name = name;
    this.age = age;
    this.thread = thread; // Thread는 직렬화 불가능
  }
}

6. 참고

  • 직렬화/역직렬화는 ObjectOutputStream, ObjectInputStream을 통해 수행합니다.
  • 직렬화는 객체의 상태만 저장하며, 메서드나 static 필드는 저장되지 않습니다.
profile
타닥..타닥

0개의 댓글