개발

JAVA - 직렬화, 역직렬화

kwony 2023. 2. 24. 15:13

직렬화, 역직렬화

 

자바의 직렬화는 객체를 바이트의 연속으로 변환시키는 기술이다. 역직렬화는 반대로 바이트의 연속을 오브젝트로 전환해주는 기술이다. 직렬화된 객체가 파일에 기록되면 역직렬화를 통해 파일에서 읽어온 데이터를 메모리에서 객체로 쓸 수 있다.

 

이렇게 인터뷰용 답변만 준비하지말고 직접 코딩해서 체험해보는게 좋다. Person이라는 클래스를 직렬화해서 file.ser이라는 파일에 바이트코드를 써보자.

 

data class Person(val name: String, val age: Int) : Serializable

try {
    val person = Person(name = "ryan", age = 30)
    val fileOut = FileOutputStream("file.ser")
    val objOut = ObjectOutputStream(fileOut)

    objOut.writeObject(person)
    objOut.close()
    fileOut.close()
} catch (e: IOException) {
    e.printStackTrace()
}

 

그러면 file.ser 이라는 파일이 생성된다. vim 에디터를 통해서 열어보면 Person의 정보가 바이트로 입력되어있다.

 

 

이제는 역직렬화를 통해서 바이트를 오브젝트로 바꿀 차례다. 파일에서 바이트코드를 읽어오고 역직렬화를 통해 Person 객체로 선언했다. 그 결과 입력했던 값과 동일한 객체를 얻어낼 수 있었다.

 

try {
    val fileIn = FileInputStream("file.ser")
    val objIn = ObjectInputStream(fileIn)
    val person = objIn.readObject() as Person

    println("person: ${person.name} ${person.age}")
} catch (e: ClassNotFoundException) {
    e.printStackTrace()
}
--------------------------------------------------------
person: ryan 30

 

serialVersionUID

 

그런데 파일에 입력했을때와 읽어올 때 클래스가 달라질 수 있다. 이미 파일에 값을 써둔 상태에서 Person 클래스에 gender라는 속성을 추가했다고 해보자. 그러면 읽어올 때 어떻게 읽어오게 될까?

 

data class Person(val name: String, val age: Int, val gender: Int) : Serializable

try {
    val fileIn = FileInputStream("file.ser")
    val objIn = ObjectInputStream(fileIn)
    val person = objIn.readObject() as Person

    println("person: ${person.name} ${person.age} ${person.gender}")
} catch (e: ClassNotFoundException) {
    e.printStackTrace()
}
--------------------------------------------------------
person: ryan 30 0

 

Exception은 발생하지 않고 값을 기록하지 않았던 gender는 0으로 세팅된다. 디폴트 값을 넣어주는 방식을 선호할 수 있지만 경우에 따라선 유효하지 않은 값이기 때문에 에러를 출력하는 것이 적절할 수도 있다.

 

자바에선 serialVersionUID 변수 값을 이용해 클래스별로 바이트 연속에 직렬화 버전 값을 넣어줄 수 있다. 역직렬화 할때는 대상인 클래스의 serialVersionUID 값이 직렬화때 사용한 값과 동일해야 정상적으로 읽어오게 된다.

 

테스트를 해보자. 직렬화 할때는 serialVersionUID 값을 1로 주고 파일에 저장했다.

 

data class Person(val name: String, val age: Int, val gender: Int) : Serializable {
    companion object {
        private const val serialVersionUID: Long = 1L
    }
}

try {
    val person = Person(name = "ryan", age = 30, gender = 1)
    val fileOut = FileOutputStream("file.ser")
    val objOut = ObjectOutputStream(fileOut)

    objOut.writeObject(person)
    objOut.close()
    fileOut.close()
} catch (e: IOException) {
    e.printStackTrace()
}

 

그리고 역직렬화 할때는 serialVersionUID를 2로 바꿨다.

 

data class Person(val name: String, val age: Int, val gender: Int) : Serializable {
    companion object {
        private const val serialVersionUID: Long = 2L
    }
}

 

그 결과 serial version 이 맞지 않는다는 에러를 띄운다. 버전 정보를 활용해서 직렬화를 관리하면 될 것 같다.