1. 컬렉션 직렬화
좋은 소식: Java의 거의 모든 표준 컬렉션(예: ArrayList, HashSet, HashMap 등)은 이미 Serializable을 구현합니다. 즉, 별다른 복잡한 작업 없이 바로 직렬화할 수 있습니다.
예시:
import java.io.Serializable;
import java.util.ArrayList;
public class MyList extends ArrayList<String> implements Serializable {
// 아무것도 쓰지 않아도 됩니다 — ArrayList는 이미 Serializable입니다!
}
실무에서는 보통 표준 컬렉션을 직렬화하며, 별다른 번거로움 없이 잘 동작합니다.
컬렉션을 어떻게 직렬화하나요?
컬렉션 직렬화는 다른 어떤 객체와도 동일한 접근을 사용합니다:
- 컬렉션을 생성한다.
- ObjectOutputStream으로 파일에 기록한다.
- ObjectInputStream으로 다시 읽어 온다.
예시 1: 직렬화 ArrayList<String>
import java.io.*;
import java.util.*;
public class SerializeListDemo {
public static void main(String[] args) throws Exception {
// 1. 문자열 목록 생성
List<String> books = new ArrayList<>();
books.add("호밀밭의 파수꾼");
books.add("위대한 유산");
books.add("신곡");
// 2. 목록을 파일에 저장
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("books.ser"));
out.writeObject(books);
out.close();
// 3. 파일에서 목록 다시 읽기
ObjectInputStream in = new ObjectInputStream(new FileInputStream("books.ser"));
List<String> loadedBooks = (List<String>) in.readObject();
in.close();
// 4. 결과 확인
System.out.println("역직렬화 후 책 목록:");
for (String book : loadedBooks) {
System.out.println("- " + book);
}
}
}
출력:
역직렬화 후 책 목록:
- 호밀밭의 파수꾼
- 위대한 유산
- 신곡
예시 2: 직렬화 HashSet<Integer>
Set<Integer> numbers = new HashSet<>(Arrays.asList(10, 20, 30, 40));
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("numbers.ser"));
out.writeObject(numbers);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("numbers.ser"));
Set<Integer> loadedNumbers = (Set<Integer>) in.readObject();
in.close();
System.out.println("역직렬화 후 집합: " + loadedNumbers);
예시 3: 직렬화 HashMap<String, Integer>
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 100);
scores.put("Bob", 80);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("scores.ser"));
out.writeObject(scores);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("scores.ser"));
Map<String, Integer> loadedScores = (Map<String, Integer>) in.readObject();
in.close();
System.out.println("역직렬화 후 맵: " + loadedScores);
중요한 포인트
순서가 보존되나요?
- List(예: ArrayList)는 요소의 순서가 항상 보존됩니다.
- Set(HashSet)은 순서가 보장되지 않습니다(직렬화 전후 모두 동일).
- Map(HashMap)은 키-값 쌍의 순서가 보장되지 않습니다(순서가 필요하면 LinkedHashMap을 사용하세요).
빈 컬렉션은 문제없이 직렬화/역직렬화됩니다. 역직렬화 후에는 비어 있는 리스트/셋/맵을 얻게 됩니다.
중첩 컬렉션(예: List<List<String>>)도 모든 중첩 요소가 직렬화 가능하다면 정상적으로 직렬화됩니다.
2. 컬렉션 요소에 대한 요구사항
여기서 자주 첫 번째 큰 함정이 기다립니다!
컬렉션의 모든 요소도 직렬화 가능해야 합니다.
요소 중 하나라도 Serializable 인터페이스를 구현하지 않으면, 컬렉션 직렬화는 NotSerializableException 오류로 끝납니다. 특히 컬렉션이 크고 오류가 내부 어딘가에서 발생하면, 원인을 찾는 일이 꽤나 긴 탐색전이 될 수 있습니다.
예시: 직렬화 불가능한 객체가 들어 있는 컬렉션 직렬화
import java.io.*;
import java.util.*;
class NotSerializableClass {
int value;
public NotSerializableClass(int value) {
this.value = value;
}
}
public class NotSerializableDemo {
public static void main(String[] args) throws Exception {
List<Object> list = new ArrayList<>();
list.add("Hello");
list.add(new NotSerializableClass(123)); // <- 앗!
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("badlist.ser"));
try {
out.writeObject(list); // 여기에서 예외가 발생합니다!
} catch (NotSerializableException e) {
System.out.println("직렬화 오류: " + e);
}
out.close();
}
}
결과:
직렬화 오류: java.io.NotSerializableException: NotSerializableClass
어떻게 해야 하나요?
해결은 간단합니다. 컬렉션의 모든 요소는 String이나 Integer 같은 표준 타입이거나, 여러분이 만든 클래스라면 인터페이스 Serializable을 구현해야 합니다. 만약 어떤 클래스가 직렬화 불가능하다면, 해당 클래스에 implements Serializable만 추가하면 문제가 해결됩니다.
3. 컬렉션별 직렬화 특징
요소 순서가 보존되나요?
- List: 네, 요소의 순서는 보존됩니다.
- Set: 구현에 따라 다릅니다. HashSet은 순서가 보장되지 않지만, LinkedHashSet은 순서가 보존됩니다.
- Map: HashMap은 순서가 보장되지 않고, LinkedHashMap은 추가 순서가 보존됩니다.
데모:
List<String> list = Arrays.asList("A", "B", "C");
Set<String> set = new HashSet<>(list);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("coll.ser"));
out.writeObject(list);
out.writeObject(set);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("coll.ser"));
List<String> loadedList = (List<String>) in.readObject();
Set<String> loadedSet = (Set<String>) in.readObject();
in.close();
System.out.println("List: " + loadedList); // 항상 [A, B, C]
System.out.println("Set: " + loadedSet); // [A, C, B] 등이 될 수 있음
빈 컬렉션 직렬화
빈 컬렉션은 문제없이 직렬화됩니다. 역직렬화 후 필요한 타입의 비어 있는 객체를 얻게 됩니다.
중첩 컬렉션 직렬화
다른 컬렉션을 포함하는 컬렉션도 직렬화할 수 있습니다:
List<List<String>> table = new ArrayList<>();
table.add(Arrays.asList("row1-col1", "row1-col2"));
table.add(Arrays.asList("row2-col1", "row2-col2"));
// 직렬화
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("table.ser"));
out.writeObject(table);
out.close();
// 역직렬화
ObjectInputStream in = new ObjectInputStream(new FileInputStream("table.ser"));
List<List<String>> loadedTable = (List<List<String>>) in.readObject();
in.close();
System.out.println(loadedTable);
4. 실전 예제: 사용자 정의 클래스 객체 컬렉션 직렬화
“가상 도서관”을 위한 작은 애플리케이션을 함께 만들어 보며, 책 목록을 직렬화해 보겠습니다. 클래스 Book은 직렬화 가능해야 합니다!
import java.io.*;
import java.util.*;
class Book implements Serializable {
private static final long serialVersionUID = 1L; // 클래스 버전 호환을 위해
String title;
String author;
int year;
public Book(String title, String author, int year) {
this.title = title;
this.author = author;
this.year = year;
}
@Override
public String toString() {
return title + " (" + author + ", " + year + ")";
}
}
public class LibraryApp {
public static void main(String[] args) throws Exception {
List<Book> books = new ArrayList<>();
books.add(new Book("호밀밭의 파수꾼", "J. D. 샐린저", 1951));
books.add(new Book("위대한 유산", "찰스 디킨스", 1861));
books.add(new Book("신곡", "단테 알리기에리", 1320));
// 목록을 파일에 저장
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("mylibrary.ser"));
out.writeObject(books);
out.close();
// 목록을 다시 읽기
ObjectInputStream in = new ObjectInputStream(new FileInputStream("mylibrary.ser"));
List<Book> loadedBooks = (List<Book>) in.readObject();
in.close();
System.out.println("파일에서 읽어 온 책:");
for (Book b : loadedBooks) {
System.out.println("- " + b);
}
}
}
5. 컬렉션 직렬화 시 흔한 오류
오류 №1: 컬렉션 요소에서 NotSerializableException. 컬렉션 요소 중 하나라도 직렬화 불가능하면, 직렬화는 NotSerializableException과 함께 실패합니다. 예를 들어, 실수로 Serializable을 구현하지 않은 객체를 컬렉션에 넣었거나, 자신의 클래스에 이 인터페이스 추가를 잊은 경우입니다.
오류 №2: 직렬화와 역직렬화 사이에 클래스가 변경됨. 객체 컬렉션을 직렬화한 뒤 클래스 구조(예: 필드 추가/삭제)를 변경하면, 역직렬화 시 InvalidClassException이 발생할 수 있습니다. 이를 피하려면 클래스에 serialVersionUID 필드를 사용하세요.
오류 №3: transient 또는 static 필드가 있는 컬렉션 직렬화. transient 또는 static으로 표시된 필드는 직렬화되지 않습니다. 이런 필드에 의존하는 객체는 역직렬화 후 기본값(예: null 또는 0)을 갖게 됩니다.
오류 №4: 중첩된 직렬화 불가능 객체가 포함된 컬렉션 직렬화. 컬렉션 안에 직렬화 불가능한 중첩 컬렉션이나 객체가 있으면, 가장 깊은 수준에서 오류가 발생합니다 — 문제 지점을 즉시 파악하기 어렵습니다. 모든 중첩 수준을 확인하세요!
오류 №5: 큰 컬렉션에서의 성능. 컬렉션이 매우 크면 직렬화에 많은 시간과 디스크 공간이 필요할 수 있습니다. 이런 경우 스트리밍 직렬화나 컬렉션을 여러 조각으로 나누는 방식을 고려하세요.
GO TO FULL VERSION