CodeGym /행동 /JAVA 25 SELF /ObjectOutputStream, ObjectInputStream: 스트림 작업

ObjectOutputStream, ObjectInputStream: 스트림 작업

JAVA 25 SELF
레벨 42 , 레슨 3
사용 가능

1. 소개

Java에서 직렬화는 이를 명시적으로 허용한 객체에 대해서만 동작합니다. 이를 위해 클래스는 특수 인터페이스인 java.io.Serializable을 구현해야 합니다.

import java.io.Serializable;

public class Person implements Serializable {
    // 필드, 생성자, 메서드
}

Serializable은 마커 인터페이스입니다. 메서드가 없으며, JVM에 “이 클래스는 직렬화할 수 있으니 걱정하지 마세요!”라고 알리는 역할만 합니다. Serializable을 구현하지 않은 클래스의 객체를 직렬화하려 하면 NotSerializableException이 발생합니다. 필드(또는 중첩 객체) 중 하나라도 직렬화할 수 없으면 직렬화는 실패합니다.

ObjectOutputStream과 ObjectInputStream

  • ObjectOutputStream은 객체를 스트림(예: 파일 또는 네트워크)으로 기록하는 클래스입니다.
  • ObjectInputStream은 스트림에서 객체를 읽어들이는 클래스입니다.

이 둘은 짝을 이룹니다. 하나가 객체를 직렬화하고, 다른 하나가 역직렬화합니다.

주요 메서드

  • writeObject(Object obj) — 객체를 직렬화하여 스트림에 기록합니다.
  • readObject() — 스트림에서 객체를 읽어 역직렬화한 뒤 반환합니다.

중요: 두 클래스는 기본 입출력 스트림(OutputStreamInputStream) 위에서 동작합니다. 가장 흔히 파일 스트림인 FileOutputStream, FileInputStream과 함께 쓰지만, 네트워크 스트림과 함께 사용할 수도 있습니다.

2. 직렬화 예제

간단한 예제를 작성해 봅시다. Person 클래스의 객체를 직렬화하고 역직렬화합니다.

1단계. 클래스 정의

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;
    // transient 필드는 직렬화되지 않음
    private transient String secret;

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

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

2단계. 객체를 파일로 직렬화

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class SerializeDemo {
    public static void main(String[] args) throws Exception {
        Person person = new Person("Alice", 30, "likes pizza");

        // 파일에 쓰기 위한 스트림 생성
        FileOutputStream fileOut = new FileOutputStream("person.bin");
        ObjectOutputStream out = new ObjectOutputStream(fileOut);

        // 객체 저장
        out.writeObject(person);

        // 스트림 닫기
        out.close();
        fileOut.close();

        System.out.println("객체가 person.bin 파일로 직렬화되었습니다");
    }
}

3단계. 파일에서 객체 역직렬화

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class DeserializeDemo {
    public static void main(String[] args) throws Exception {
        // 파일에서 읽기 위한 스트림 열기
        FileInputStream fileIn = new FileInputStream("person.bin");
        ObjectInputStream in = new ObjectInputStream(fileIn);

        // 객체 복원
        Person person = (Person) in.readObject();

        in.close();
        fileIn.close();

        System.out.println("객체가 역직렬화되었습니다: " + person);
    }
}

예상 출력

객체가 person.bin 파일로 직렬화되었습니다
객체가 역직렬화되었습니다: Person{name='Alice', age=30, secret='null'}

주의! transient 필드는 직렬화되지 않습니다. 역직렬화 후에는 null이 됩니다. 이는 일시적이거나 민감한 데이터를 다룰 때 중요합니다.

3. 제한 사항과 특징

모든 필드는 직렬화 가능해야 함

클래스에 Serializable을 구현하지 않은 필드(또는 그런 객체를 담는 필드)가 있으면 직렬화는 오류로 끝납니다. 예를 들어 ThreadSocket 같은 타입은 “그냥” 직렬화 가능하게 만들 수 없습니다.

static 필드와 transient 필드

  • static 필드(static)는 직렬화되지 않습니다. 특정 객체가 아니라 클래스에 속하기 때문입니다.
  • transient 필드transient로 표시된 필드는 명시적으로 직렬화에서 제외되며, 복원 후 기본값(null, 0 등)을 가집니다.

예외

  • Serializable을 구현하지 않은 객체를 직렬화하려 하면 NotSerializableException이 발생합니다.
  • 역직렬화 중에는 파일 없음, 클래스 불일치, 데이터 손상 등 다양한 오류가 발생할 수 있습니다.

클래스 버전

직렬화 후 클래스 구조를 변경하면(예: 필드 추가/삭제) 역직렬화 시 InvalidClassException이 발생할 수 있습니다. 버전 관리를 위해 특별한 필드 serialVersionUID를 사용합니다(자세한 내용은 이후 강의에서 다룹니다).

4. 실습: 객체를 파일로 직렬화/역직렬화

Person 클래스가 있고, 사람 목록을 파일에 저장했다가 다시 읽어 오고 싶다고 가정해 봅시다.

Person 클래스 (직렬화 가능)

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;
    private transient String secret; // 직렬화되지 않음

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

    @Override
    public String toString() {
        return name + " (" + age + "), secret: " + secret;
    }
}

사람 목록 직렬화

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

public class SerializeListDemo {
    public static void main(String[] args) throws Exception {
        List<Person> people = new ArrayList<>();
        people.add(new Person("Alice", 30, "likes pizza"));
        people.add(new Person("Bob", 25, "hates broccoli"));

        FileOutputStream fileOut = new FileOutputStream("people.bin");
        ObjectOutputStream out = new ObjectOutputStream(fileOut);

        // 목록 저장
        out.writeObject(people);

        out.close();
        fileOut.close();

        System.out.println("사람 목록이 직렬화되었습니다.");
    }
}

사람 목록 역직렬화

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;

public class DeserializeListDemo {
    public static void main(String[] args) throws Exception {
        FileInputStream fileIn = new FileInputStream("people.bin");
        ObjectInputStream in = new ObjectInputStream(fileIn);

        // 목록 복원
        List<Person> people = (List<Person>) in.readObject();

        in.close();
        fileIn.close();

        for (Person p : people) {
            System.out.println(p);
        }
    }
}

결과:

Alice (30), secret: null
Bob (25), secret: null

5. 흔한 실수

오류 №1: 클래스가 Serializable을 구현하지 않음. implements Serializable을 추가하는 것을 잊으면 직렬화 시 NotSerializableException이 발생합니다. 가장 흔하고 간단한 실수입니다.

오류 №2: 직렬화할 수 없는 필드. 객체에 직렬화할 수 없는 필드가 있으면(예: Thread, Socket 또는 Serializable이 없는 기타 타입) 직렬화가 실패합니다. 이런 필드는 transient로 표시하거나 직렬화 가능하게 만드세요.

오류 №3: 클래스 구조 변경. 객체를 직렬화한 뒤 클래스가 변경되면(필드 추가/삭제 등) 읽기 시 InvalidClassException이 발생할 수 있습니다. 안정적인 버전 관리를 위해 serialVersionUID를 지정하세요.

오류 №4: static 필드를 직렬화하려는 시도. static 필드는 직렬화되지 않습니다. 역직렬화 후 이 값은 직렬화 당시의 값이 아니라 클래스에 정의된 기본값을 따릅니다.

오류 №5: 스트림을 닫지 않음. 작업 후 스트림을 닫지 않으면 파일이 손상되거나 리소스가 누수될 수 있습니다. try-with-resources를 사용하거나 스트림을 명시적으로 닫으세요.

오류 №6: 클래스 불일치. 클래스 이름을 변경했거나 다른 패키지로 옮긴 경우 역직렬화가 동작하지 않습니다. 스트림에 저장된 클래스의 이름과 패키지가 정확히 일치해야 합니다.

1
과제
JAVA 25 SELF, 레벨 42, 레슨 3
잠금
간단한 객체 직렬화
간단한 객체 직렬화
1
과제
JAVA 25 SELF, 레벨 42, 레슨 3
잠금
transient 필드와 직렬화
transient 필드와 직렬화
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION