
최근 자바 공부를 하면서 여러 클래스를 보던 중 String 클래스와 ArrayList 클래스 등 많은 클래스가 Serializable 인터페이스를 구현한 것을 알 수 있었습니다. 그래서 이번 기회에 이 Serializable 인터페이스가 어떤 역할을 하는지 알아보고자 합니다.
직렬화와 역직렬화
- 직렬화: 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부 자바 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터를 변환하는 기술
- 역직렬화: 바이트(byte) 형태로 변환된 데이터를 다시 객체로 변환하는 기술
Serializable 인터페이스
Serializable은 직렬화를 위한 인터페이스로 객체를 파일에 저장하거나, 다른 서버로 보내거나 받거나 등의 일을 하기 위해 구현해야 하는 인터페이스입니다.
Serializable 인터페이스를 보면 메소드가 하나도 없는데, 이러한 인터페이스를 Marker 인터페이스라고 합니다.
Marker Interface: 변수와 메서드를 정의하지 않은 인터페이스로 자신을 구현하는 클래스가 특정 속성을 가짐을 표시해 주는 인터페이스
Serializable 인터페이스 사용하기
다음은 직렬화하기 위한 객체인 Book 클래스를 작성하였습니다.
객체를 직렬화하기 위해서는 java.io.Serializable 인터페이스를 상속받아야 합니다.
import java.io.Serializable;
public class Book implements Serializable {
private int bookNum;
private String title;
private int price;
public Book (int bookNum, String title, int price) {
this.bookNum = bookNum;
this.title = title;
this.price = price;
}
@Override
public String toString() {
return "Book{" + "bookNum=" + bookNum + ", title: " + title + ", price: " + price + "}";
}
}
1. 직렬화
public class Main {
public static void main(String[] args) throws IOException {
Book book = new Book(123, "자바", 10000);
byte[] serializedBook;
// 객체를 직렬화하여 byte 배열로 변환
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
// Book 객체를 직렬화하여 ObjectOutputStream에 쓰기
oos.writeObject(book);
// 직렬화된 데이터를 바이트 배열로 변환
serializedBook = baos.toByteArray();
}
}
// 직렬화된 byte 배열을 Base64로 인코딩하여 출력
System.out.println(Base64.getEncoder().encodeToString(serializedBook));
}
}
결과
2. 역직렬화
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String base64Book = "rO0ABXNyAAl0ZXN0LkJvb2tPpK+HY54GUAIAA0kAB2Jvb2tOdW1JAAVwcmljZUwABXRpdGxldAASTGphdmEvbGFuZy9TdHJpbmc7eHAAAAB7AAAnEHQABuyekOuwlA==";
// Base64 인코딩된 문자열을 바이트 배열로 디코딩
byte[] serializedBook = Base64.getDecoder().decode(base64Book);
try (ByteArrayInputStream bais = new ByteArrayInputStream(serializedBook)) {
try (ObjectInputStream ois = new ObjectInputStream(bais)) {
// 역직렬화된 Book 객체를 읽음
Object objectBook = ois.readObject();
Book book = (Book) objectBook;
System.out.println(book);
}
}
}
}
결과
SerialVersionUID
클래스에 author 필드를 추가하면 어떻게 될지 궁금하여 다음과 같이 Book 클래스를 수정하였습니다.
import java.io.Serializable;
public class Book implements Serializable {
private int bookNum;
private String title;
private int price;
private String author;
public Book (int bookNum, String title, int price, String author) {
this.bookNum = bookNum;
this.title = title;
this.price = price;
this.author = author;
}
@Override
public String toString() {
return "Book{" + "bookNum=" + bookNum + ", title: " + title + ", price: " + price + "}";
}
}
그리고 역직렬화 코드를 그대로 실행하니 아래와 같은 에러가 발생했습니다.
Exception in thread "main" java.io.InvalidClassException: test.Book; local class incompatible: stream classdesc serialVersionUID = 5738904821203600976, local class serialVersionUID = 3637235769894501927
at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:601)
at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2062)
at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1909)
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235)
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1744)
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:514)
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:472)
at test.Main.main(Main.java:17)
에러 메시지를 확인하니 serialVersionUID가 일치하지 않는다고 합니다.
serialVersionUID는 클래스의 버전 관리를 위해 사용됩니다.
이는 각 서버들이 쉽게 해당 객체가 같은지를 확인할 수 있도록 하기 위해 사용되는데, serialVersionUID는 따로 명시하지 않으면 클래스의 기본 해시값을 사용하게 됩니다.
위의 코드에서 author 필드를 추가했기 때문에, 클래스 구조가 변경되었고, 이로 인해 직렬화된 Book 객체의 serialVersionUID와 현재 클래스의 serialVersionUID가 일치하지 않게 되어 에러가 발생한 것입니다.
이 문제를 해결하려면 다음과 같이 Book 클래스에 명시적으로 serialVersionUID를 정의해야 합니다.
package test;
import java.io.Serializable;
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
private int bookNum;
private String title;
private int price;
public Book (int bookNum, String title, int price) {
this.bookNum = bookNum;
this.title = title;
this.price = price;
}
@Override
public String toString() {
return "Book{" + "bookNum=" + bookNum + ", title: " + title + ", price: " + price + "}";
}
}
직렬화 코드를 통해 Book 객체를 직렬화하고, Book 클래스에 author 필드를 추가하였습니다.
import java.io.Serializable;
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
private int bookNum;
private String title;
private int price;
private String author;
public Book (int bookNum, String title, int price, String author) {
this.bookNum = bookNum;
this.title = title;
this.price = price;
this.author = author;
}
@Override
public String toString() {
return "Book{" + "bookNum=" + bookNum + ", title: " + title + ", price: " + price + ", author: " + author + "}";
}
}
역직렬화 코드를 실행하니 다음과 같은 결과가 나온 것을 확인할 수 있었습니다.
주의!!
새로운 필드 추가는 무시하여 에러를 발생시키지 않지만, 기존 필드의 타입을 변경하면 에러를 발생시킵니다.
자바 직렬화의 장단점
장점
- 복잡한 데이터 구조의 클래스의 객체라도 직렬화 기본 조건만 지키면 큰 작업 없이 바로 직렬화가 가능합니다.
- 서로 다른 개발 환경을 가진 자바 데이터의 경우에도 동일한 자바 시스템을 갖추고 있다면, 직렬화와 역직렬화를 통해 데이터를 서로 전송하는 것이 가능합니다.
단점
- 직렬화는 객체에 저장된 데이터값 외에도 타입에 대한 정보 등 클래스의 메타 정보 등을 함께 저장하기에 데이터 용량이 다른 포맷에 비해 큰 문제가 있습니다. (JSON과 비교하면 동일 자원 저장 대비 최소 2배 이상 차이가 난다)
참고자료
자바 직렬화, 그것이 알고싶다. 훑어보기편 | 우아한형제들 기술블로그
{{item.name}} 자바의 직렬화 기술에 대한 대한 이야기입니다. 간단한 질문과 답변 형태로 자바 직렬화에 대한 간단한 설명과 직접 프로젝트를 진행하면서 겪은 경험에 대해 이야기해보려 합니다.
techblog.woowahan.com
· JAVA 직렬화(Serialization)과 역직렬화(Deserialization)
👩🏻💻 지식 창고 📚
inkyu-yoon.github.io
'Java' 카테고리의 다른 글
[Java] Scanner Vs BufferedReader (2) | 2024.08.11 |
---|---|
[Java] Arrays.sort() 알아보기 (0) | 2023.04.26 |
느리더라도 단단하게 성장하고자 합니다!
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!