Java - 직렬화(Serialize), 역직렬화(Deserialize)

자바에서 직렬화(Serialize)는 객체를 byte로 변환하는 것을 말합니다. byte로 변환하게 되면 현재 객체의 상태 그대로 파일로 저장하거나(캐싱), 네트워크를 통해 다른 시스템으로 전달할 수 있습니다.

역직렬화(Deserialize)는 직렬화된 byte 데이터를 다시 객체로 변환하는 것을 말합니다.

직렬화가 사용되는 예를 들어 보면, 자바 프로그램이 세션 정보를 객체로 갖고 있는데, 프로그램이 재시작될 때 이전에 갖고 있던 세션 정보를 계속 유지하고 싶습니다. 이것을 구현하기 위해, 첫번째 방법은 세션 객체에서 모든 데이터를 JSON과 같은 형식의 텍스트로 변환해서, 그것을 파일에 저장할 수 있습니다. 그리고 프로그램이 재시작되면 이전에 저장된 파일에서 데이터를 읽고 그 데이터로 객체를 다시 생성할 수 있습니다.

두번째 방법은 자바 객체를 직렬화하여 파일로 저장하고, 프로그램 재시작 시 역직렬화하여 자바 객체로 변환하는 것입니다. 취향에 따라 구현하면 되겠지만, 자바에서만 동작하는 프로그램이라면 직렬화를 이용하는 것이 간단할 수 있습니다.

1. 객체를 직렬화(Serialize)하는 방법

java.io.Serializable를 구현하는 클래스만 직렬화 할 수 있습니다. 만약 어떤 객체를 직렬화해야 한다면 그 클래스가 Serializable을 구현하고 있는지 확인해야 합니다.

아래의 Student 클래스는 Serializable을 구현하기 때문에 직렬화할 수 있습니다. Serializable을 구현할 때 단순히 implements Serializable만 입력하면 됩니다. 추상 메소드를 Override하여 구현할 것은 없습니다.

import java.io.Serializable;

public class Student implements Serializable {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student { name: " + name + ", age: " + age + " }";
    }
}

주의할 점은, Serializable를 구현하는 클래스는 (기본 자료형을 제외한) 하위 변수들의 클래스가 모두 Serializable를 구현해야 합니다. 예를 들어, Student는 하위에 Address 클래스의 변수를 갖고 있는데 Address가 Serializable를 구현하지 않으면 직렬화가 실패합니다.

import java.io.Serializable;

public class Student implements Serializable {
    public String name;
    public int age;
    public Address;  // need to implement 'Serializable'

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

직렬화를 시도해보면, 아래와 같이 NotSerializableException이 발생합니다.

> Task :Example.main() FAILED
Exception in thread "main" java.io.NotSerializableException: Address
	at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1197)

2. 객체를 직렬화하여 파일로 저장

ObjectOutputStream.writeObject(object) 메소드는 Serializable을 구현하는 클래스의 객체 object를 직렬화합니다. 만약 직렬화한 byte를 파일에 저장하려면, FileOutputStream과 함께 사용하면 됩니다.

아래 예제는 위에서 소개한 Student 클래스의 객체 s1과 s2를 직렬화하고 파일로 저장하는 예제입니다.

import java.io.*;

public class Example {

    public static void main(String[] args) throws IOException {
        Student s1 = new Student("John", 33);
        Student s2 = new Student("Doe", 30);

        FileOutputStream fileOutputStream
                = new FileOutputStream("/tmp/output");
        ObjectOutputStream objectOutputStream
                = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(s1);
        objectOutputStream.writeObject(s2);
        objectOutputStream.close();
    }
}

/tmp/output 파일을 확인해보면 아래와 같이 바이트로 저장이 됩니다.

aced 0005 7372 0007 5374 7564 656e 7408
0519 b243 26d1 0f02 0002 4900 0361 6765
4c00 046e 616d 6574 0012 4c6a 6176 612f
6c61 6e67 2f53 7472 696e 673b 7870 0000
0021 7400 044a 6f68 6e73 7100 7e00 0000
0000 2174 0003 446f 65

3. 역직렬화하여 객체로 변환

ObjectInputStream.readObject()는 byte를 읽고 자바 객체로 역직렬화합니다. 직렬화된 파일을 읽고 역직렬화할 때는 FileInputStream와 함께 사용하면 됩니다.

아래 예제는 위에서 저장한 직렬화된 파일 /tmp/output의 바이트를 역직렬화하여 자바 객체로 변환하는 코드입니다. 역직렬화할 때는 직렬화한 객체 순서대로 역직렬화해야 합니다. 다양한 클래스를 직렬화했을 때, 역직렬화 순서가 다르면 역직렬화에 실패합니다.

import java.io.*;

public class Example1 {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        FileInputStream fileInputStream
                = new FileInputStream("/tmp/output");
        ObjectInputStream objectInputStream
                = new ObjectInputStream(fileInputStream);
        Student s1 = (Student) objectInputStream.readObject();
        Student s2 = (Student) objectInputStream.readObject();
        objectInputStream.close();

        System.out.println(s1.toString());
        System.out.println(s2.toString());
    }
}

Output:

Student { name: John, age: 33 }
Student { name: Doe, age: 30 }

4. transient로 멤버 변수를 직렬화 대상에서 제외하기

Serializable를 구현하는 클래스는 하위에 모든 클래스가 Serializable를 구현해야 하고, 그렇지 않으면 직렬화 과정에서 에러가 발생한다고 했습니다.

하지만 직렬화가 필요하지 않은 멤버 변수는 클래스 선언에 transient를 사용하여 직렬화 대상에서 제외시킬 수 있습니다. 이렇게 하면 해당 변수는 직렬화되지 않고, Serializable을 구현하지 않아도 됩니다.

아래 예제에서 Address는 transient로 선언되어 직렬화되지 않으며, Address 클래스는 Serializable를 구현하지 않아도 직렬화하는데 문제가 없습니다.

import java.io.Serializable;

public class Student implements Serializable {
    public String name;
    public int age;
    public transient Address address = new Address("New York");

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
Loading script...
codechachaCopyright ©2019 codechacha