Java 직렬화(Serialization) 란?
Java 직렬화에 앞서, 데이터 직렬화에 대해 먼저 말해보자면, 소프트웨어 개발에서의 데이터 직렬화란 메모리를 디스크에 저장하거나 네트워크 통신에 사용하기 위한 형식으로 변환하는 것이다.
그렇다면 자바 직렬화는 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터를 변환하는 기술과 바이트로 변환된 데이터를 다시 객체로 변환하는 기술(역직렬화)을 아울러 말할 수 있다.
직렬화(Serialization)를 하는 이유는 뭘까?
데이터를 그냥 사용하지 않고 직렬화해서 사용하는 이유는 무엇일까?
개발언어에서 사용하는 메모리 구조는 크게 값 형식 데이터(Value Type)과 참조 형식 데이터(Reference Type)로 나뉜다.
- 값 형식 데이터(Value Type) : 기본형 데이터 타입이라 불리는 int, float, char 등의 값 형식 데이터들은 Stack 영역에 메모리가 쌓이고 직접 접근이 가능하다.
- 참조 형식 데이터(Reference Type) : 참조 형식 데이터를 선언하면 Heap 영역에 메모리가 할당되고 Stack 영역에서는 이 Heap 메모리를 참조하는 구조로 되어있다.
이 두 가지 데이터 타입 중에서 값 형식 데이터(Value Type)만이 디스크에 저장하거나 통신에 사용 가능하다.
참조 형식 데이터(Reference Type)는 실제 데이터 값이 아닌 Heap 영역에 할당되어 있는 메모리 주소를 가지고 있기 때문에 저장 및 통신에 사용할 수 없다.
직렬화를 하게 되면 각 주소 값이 가지는 데이터들을 전부 값 형식(Value Type) 데이터로 변환해준다.
Java 직렬화(Serialization) 예시
Java 기본타입(primitive type)과 java.io.Serializable 인터페이스를 상속받은 객체는 직렬화 할 수 있는 기본 조건을 갖는다.
import java.io.Serializable;
public class Member implements Serializable {
private String name;
private String email;
private int age;
public Member(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
//Getter 생략
@Override
public String toString() {
return "Member{" +
"name='" + name + '\'' +
", email='" + email + '\'' +
", age=" + age +
'}';
}
}
객체를 직렬화 하기 위해서는 java.oi.ObjectOutputStream 을 사용한다.
public class Ex00_0 {
//직렬화
public static void main(String[] args) {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("test.dat"));
Member member1 = new Member("TONY", "tony@abc.com", 39);
Member member2 = new Member("CHRIS", "chris@def.com", 20);
oos.writeObject(member1);
oos.writeObject(member2);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
실행 결과는 다음과 같이 바이트 형식의 객체로 저장되었다.
Java 역직렬화(Deserialization) 예시
객체를 역직렬화 하기 위해서는 java.io.ObjectInputStream 을 사용한다.
java.io.ObjectInputStream 클래스를 이용하여 파일에서 객체를 역직렬화해 읽어 올 때는 더는 읽을 데이터가 없으면 EOFException이 발생하게 됩니다. 따라서 try~catch 구문을 이용하여 EOFException을 잡는 catch 블록을 만들고 파일을 끝까지 읽었을 때 에러 처리를 한다.
public class Ex00_0 {
//역직렬화
public static void main(String[] args) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("test.dat"));
Member member;
while ((member = (Member) ois.readObject()) != null) {
System.out.println(member);
}
} catch (EOFException e){
System.out.println("읽을 데이터가 없습니다.");
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
//Member{name='TONY', email='tony@abc.com', age=39}
//Member{name='CHRIS', email='chris@def.com', age=20}
serialVersionUID 는 왜 선언할까?
여러 직렬화 예시 코드를 보면 직렬화를 적용하려는 클래스에 다음과 같이 SerialVersionUID를 선언한 것을 볼 수 있다.
왜 serialVersionUID를 선언하는 것일까?
private static final long serialVersionUID = 1L;
위의 Member 객체에 필드값을 추가해보자
public class Member implements Serializable {
private String name;
private String email;
private int age;
//phone 속성을 추가
private String phone;
//생략
}
이전에 생성은 직렬화된 데이터를 역직렬화 하게 되면 phone 멤버 변수가 추가되어도 기존의 멤버 변수는 채워질 것 같지만 다음과 같은 에러를 확인할 수 있다.
java.io.InvalidClassException: Member; local class incompatible: stream classdesc serialVersionUID = -1203921720486116930, local class serialVersionUID = 565078652098517094
위의 에러 메세지를 확인해보면 serialVersionUID의 정보가 일치하지 않기 때문에 발생했음을 알 수 있다.
하지만 우리는 Member 클래스에서의 serialVersionUID 값을 -1203921720486116930 로 설정한 적도, 565078652098517094로 변경한 적도 없다.
자바 직렬화 스펙에 따르면 다음과 같은 정보를 얻을 수 있다.
- SUID(serialVersionUID) 필수 값은 아니다.
- 호환 가능한 클래스는 SUID값이 고정되어 있다.
- SUID가 선언되어 있지 않으면 클래스의 기본 해쉬값을 사용한다.
public class Member implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String email;
private int age;
private String phone;
}
serialVersionUID를 기술하지 않더라도 자동으로 해쉬값이 적용되고, 그 해쉬값을 클래스의 구조의 영향을 받는다고 한다.
따라서 직접 serialVersionUID 값을 관리해주어야 클래스 변경 시 혼란을 줄일 수 있다.
참고
https://techblog.woowahan.com/2550/
'Java' 카테고리의 다른 글
JAVA POI 라이브러리에 Reflection을 더해보자! (0) | 2023.06.03 |
---|---|
Enum 에 대해 알아보자! (0) | 2023.05.07 |
Java Stream API를 사용해보자! (0) | 2022.05.29 |