Serialize & Deserialize in Java
자바에서의 직렬화 개념과 사용법을 간단하게 공부하고 정리한 글입니다.
직렬화가 필요한 이유?
데이터의 메모리 구조는 크게 2가지로 나뉜다.
1) 값 형식 데이터 : int, float, char 등 값 형식 데이터 -> 스택에 메모리가 쌓여서 직접 접근이 가능하다.
2) 참조 형식 데이터 : 배열, 클래스, 인터페이스 등 객체를 선언하면 힙에 메모리가 할당되고 스택에서 이 메모리를 참조한다.
값 형식의 데이터는 스택 메모리에 저장되기 때문에 바로바로 접근해서 가져오는 것이 가능하다.
참조 형식 데이터는 힙에 할당되어있는 메모리 번지 주소를 가지고 있는 것이라서 직접 접근이 안된다.
이때 직렬화를 하게 되면, 메모리의 주소 값이 가지는 데이터들을 값 형식(byte)으로 변환해줌으로써 직접 접근이 가능하게 해 준다.
이에 따라 직렬화가 된 데이터는 프로그래밍 언어에 따라서 저장하거나 통신할 때 파싱 할 수 있는 데이터가 된다.
자바에서 직렬화란?
- 자바 시스템 내부에서 사용하는 객체나 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 byte 형태로 데이터를 변환하는 기술이다.
- JVM 에 상주하고 있는 객체 데이터 -> byte 코드로 변환
역직렬화란?
- byte로 된 데이터를 반대로 객체(POJO) 형식으로 파싱 하여 변환하는 기술을 역직렬화라고 한다.
- 직렬화된 byte 코드로 된 데이터 -> JVM
자바 객체를 직렬화 가능하게 만드는 방법
1. Serializable 인터페이스를 implements
2. Serializable 을 implements 한 클래스를 상속받았을 경우
public class Member implements Serializable {
private String id;
private String password;
...
}
클래스가 가지고있는 객체 인스턴스가 직렬화될 수 없는 객체라면(Serializable을 implements 하지 않음) 해당 클래스도 직렬 화가 될 수 없다. 때문에 클래스가 만약 어떠한 객체를 참조하고 있다면, 참조된 객체도 직렬화가 가능한 상태이어야 한다.
public class Member implements Serializable {
private String id;
private String password;
Team teamInfo; // serializable 이 가능한 객체여야 한다.
...
}
transient 선언하여 직렬화 대상에서 특정 멤버변수 제외시키기
public class Member implements Serializable {
private String id;
private transient String password;
...
}
SerialVersionUID란?
직렬화를 하게 되면 자바에서는 내부에서 자동으로 유니크한 번호를 생성하여 관리하는데 이 번호가 SerialVersionUID(SUID)이다.
SUID는 클래스의 멤버 변수, 이름, 생성자 등과 같이 내부 구조를 이용해서 생성된다. 때문에 역직렬화를 할 경우에 직렬화 했을 때의 SUID와 역직렬화를 할 때의 SUID를 비교하고 값이 다르다면 InvalidClassException 예외가 발생하게 된다.
때문에 자바에서는 개발자들이 SUID값을 직접 선언하고 관리하는 방식을 권장한다. SUID값을 클래스에 직접 선언하게 된다면 역 직렬화 시 클래스 구조가 달라져도 오류가 발생하지 않는다.
그러나 이러한 관점으로 보았을 때 애초에 자주 변경될 소지가 있는 클래스의 객체는 사용하지 않는 것이 바람직하다고 한다. 그리고 프레임워크나 라이브러리에서 제공되는 클래스의 객체도 버전업을 통한 SUID가 변경되는 경우가 있기 때문에 예상치 못한 오류가 발생할 수 있기 때문이다.
public class Member implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String password;
...
}
직렬화를 사용할 시 주의해야 하는 경우들
- 멤버 변수 추가 : 클래스에 스트림에 없는 필드가 생기면 해당 필드는 기본값(ex null)으로 초기화된다.
- 멤버 변수 삭제 : 값 자체가 사라짐
- 멤버 변수의 이름 변경 : 역직렬화시 오류는 안 나지만 값이 할당되지 않음
- 멤버 변수 타입 변경 : 역직렬화 과정에서 ClassCastException 이 발생할 수 있음
- 접근 지정자 변경 : public, protected 등과 같은 접근 지정자의 변경은 직렬화에 영향 안 줌
- static : static이었던 멤버가 직렬화 후 non-static으로 변경되면 직렬화 값이 무시된다.
- transiten : transiten을 직렬화 시 선언하면 이미 직렬화 대상에서 제외된 것이므로 역직렬화시에 transient 선언을 제외하더라도 값이 채워지지 않는다.
https://madplay.github.io/post/java-serialization-advanced