프로그래밍 노트
  • 자바 직렬화(Serialization)와 역직렬화(Deserialization)
    2024년 12월 19일 14시 29분 51초에 업로드 된 글입니다.
    작성자: Jeyeon

    자바에서 직렬화는 객체 혹은 데이터를 전송 또는 저장하기 위해 Byte Array로 변환하는 과정을 말한다. 역직렬화는 반대로 변환된 Byte Array를 객체 혹은 데이터로 변환하는 과정이다.

    직렬화(Serialization)

    직렬화 방법은 아래와 같다.

    public static byte[] serialize(Object obj){
        try(ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos)){
            oos.writeObject(obj);
            return baos.toByteArray();
        }catch(IOException e){
            e.printStackTrace();
        }
        return null;
    }

    여기서는 직렬화된 byte array를 반환하기 위해 ByteArrayOutputStream 객체를 사용했지만, 직렬화된 byte array를 바로 파일에 쓸 때는 FileOutputStream을 사용해도 된다.

    public class App {
        public static void main(String[] args) {
            byte[] data = serialize(new Student("John", 20));
            System.out.println(new String(data));
        }
    
        public static byte[] serialize(Object obj){
            try(ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos)){
                oos.writeObject(obj);
                return baos.toByteArray();
            }catch(IOException e){
                e.printStackTrace();
            }
            return null;
        }
    }
    class Student{
        private String name;
        private int age;
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    

    Student라는 객체를 만들어서 직렬화하는 소스코드이다. 실행 결과는 아래와 같다.

    java.io.NotSerializableException: Student
            at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1200)
            at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:358)
            at App.serialize(App.java:22)
            at App.main(App.java:15)
    

    위처럼 예외가 발생하게 된다. 예외 종류는 NotSerializableException으로 객체가 직렬화될 수 없다는 예외이다. ”직렬화될 수 없다”라는 건 그럼 직렬화될 수 있는 객체 종류가 따로 있다는 뜻이 된다.

    Serializable Interface

    사실 자바에서 지원하는 직렬화는 Serializable 인터페이스 구현체만 가능하다.

    class Student implements Serializable { //이 부분
        private String name;
        private int age;
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    

    이렇게 직렬화하고자 하는 클래스에 Serializable 인터페이스를 구현하면 된다.

    public static void main(String[] args) {
        byte[] data = serialize(new Student("John", 20));
        System.out.println(new String(data));
        //실행 결과 : ??srStudentp?|YV?? IageLnametLjava/lang/String;xptJohn
    }
    

    하지만 이 방법이 작동하지 않는 예외적인 상황이 있다. 직렬화하고자 하는 객체의 필드 변수 중 직렬화 불가한 데이터 타입을 가진 경우이다. 그 예시를 한번 보자.

    class Phone {
        private String number;
        private String type;
    
        public Phone(String number, String type) {
            this.number = number;
            this.type = type;
        }
    }
    
    class Student implements Serializable {
        private String name;
        private int age;
        private Phone phone;
    
        public Student(String name, int age, Phone phone) {
            this.name = name;
            this.age = age;
            this.phone = phone;
        }
    }
    

    Student 클래스는 Serializable 구현 클래스이지만 필드 변수에 있는 Phone은 Serializable 구현 클래스가 아니다. 이런 경우에 직렬화를 하게 되면 아래와 같은 결과를 볼 수 있다.

    java.io.NotSerializableException: Phone
            at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1200)
            at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1585)
            at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1542)
            at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1451)
            at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1194)
            at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:358)
            at App.serialize(App.java:22)
            at App.main(App.java:15)
    

    직렬화하고자 하는 클래스에 Serializable 구현을 하더라도 그 필드 변수들 또한 Serializable 해야 한다는 것을 알 수 있다.

    이제 자바에서 기본적으로 제공하는 클래스들이 Serializable인지 알아야 한다.

    자바에 Built-In 된 대부분의 클래스는 Serializable 하다. 기본 데이터 타입인 int, double, char는 객체가 아니라 값이므로 관계가 없지만 기본 데이터 타입의 Wrapper 클래스(Integer, Double, Character, Double 등)는 객체이며 Serializable 하다. 또한 ArrayList, HashMap, HashSet과 같은 컬렉션 클래스, String, Date, BigInteger, BigDecimal 같은 문자열 및 기본 객체, Exception과 같은 예외 클래스 모두 Serializable 하다.

    Thread, Socket, InputStream, OutputStream, Graphics와 같이 스레드, I/O 관련 리소스, UI 요소 리소스는 Serializable 하지 않다.

    이런 것들을 소스코드 단에서 검사할 수 있는 방법이 있다.

    Thread thread = new Thread();
    if(thread instanceof Serializable) {
        System.out.println("Thread is serializable");
    } else {
        System.out.println("Thread is not serializable");
    }
    //실행 결과 : Thread is not serializable
    

    역직렬화(Deserialization)

    역직렬화 방법은 아래와 같다.

    public static Object deserialize(byte[] data){
        try(ByteArrayInputStream bais = new ByteArrayInputStream(data);
        ObjectInputStream ois = new ObjectInputStream(bais)){
            return ois.readObject();
        }catch(IOException | ClassNotFoundException e){
            e.printStackTrace();
        }
        return null;
    }
    

    여기서는 byte array를 받아 역직렬화를 수행하기 때문에 ByteArrayInputStream 객체를 사용했다. 만약 파일을 읽어서 바로 역직렬화를 수행할 경우 FileInputStream 객체를 사용하여도 무관하다.

    public class App {
        public static void main(String[] args) {
            byte[] data = serialize(new Student("John", 20));
            Student student = (Student) deserialize(data);
            System.out.println(student.name);
        }
    
        public static byte[] serialize(Object obj){
            try(ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos)){
                oos.writeObject(obj);
                return baos.toByteArray();
            }catch(IOException e){
                e.printStackTrace();
            }
            return null;
        }
    
        public static Object deserialize(byte[] data){
            try(ByteArrayInputStream bais = new ByteArrayInputStream(data);
            ObjectInputStream ois = new ObjectInputStream(bais)){
                return ois.readObject();
            }catch(IOException | ClassNotFoundException e){
                e.printStackTrace();
            }
            return null;
        }
    }
    
    class Student implements Serializable {
        public String name;
        public int age;
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    

    실제 실행 결과는 “John”이다 객체가 정상적으로 역직렬화되어 객체로 접근이 가능한 모습이다.

    Base64 얹어 사용하기

    아래는 Base64 Encoding/Decoding을 함께 얹은 직렬화/역직렬화 메소드이다.

    //Serialize with base64 encoding
    public static String serialize(Object obj){
        try(ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos)){
            oos.writeObject(obj);
            return Base64.getEncoder().encodeToString(baos.toByteArray());
        }catch(IOException e){
            e.printStackTrace();
        }
        return null;
    }
    //Deserialize with base64 decoding
    public static Object deserialize(String data){
        try(ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(data));
        ObjectInputStream ois = new ObjectInputStream(bais)){
            return ois.readObject();
        }catch(IOException | ClassNotFoundException e){
            e.printStackTrace();
        }
        return null;
    }

    'Language > JAVA' 카테고리의 다른 글

    Java 21 Virtual Thread 알아보기  (1) 2025.04.11
    Java 패턴 매칭(Pattern Matching)  (0) 2025.03.25
    Java 16 Record class  (1) 2025.03.24
    Java 17 sealed class (sealed - permit 예약어)  (0) 2025.03.24
    댓글