직렬화(Serialization, Serializable), 역직렬화(Deserialization)

박영준·2023년 1월 22일
0

Java

목록 보기
41/111

1. 예시

전화 상으로만 강아지 A를 설명하고자 한다.

이 때,
강아지 A의 모든 특성을 가진 객체를 생성해서 직렬화한 후
통화 상대방에게 말하면 보다 간단하게 전달 가능하다.

{ "name":"Rex", "age":5, "favourite_food": pedigree_choice_cuts, "favourite_game": fetch_ball, "favourite_hobby": wagging_tail }

2. 정의

  • 직렬화
    • 직렬자바의 객체를 외부 데이터로 저장하는 것
    • 객체화된 클래스(인스턴스)의 속성과 데이터를 파일화하여, 외부에 저장할 수 있음
    • 자바 시스템 내부에서 사용되는 객체 or 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트 데이터(연속적인serial 데이터) 형태로 변환
    • JVM(Java Virtual Machine)의 메모리에 상주(Heap or Stack)되어 있는 객체 데이터를 바이트 형태로 변환
  • 역직렬화
    • 직렬화로 저장된 파일을 다시 자바의 객체로 만드는 것
    • 오버라이딩과 비슷한 개념
    • 바이트로 변환된 데이터를 다시 객체로 변환
    • 직렬화된 바이트 형태의 데이터를 객체로 변환해서 JVM으로 상주시키는 형태로 변환

즉, 직렬화 & 역직렬화란?
객체(보통 인스턴스를 의미)의 속성과 데이터를 모두 파일로 저장했다가, 필요할 때 다시 객체로 되돌리는 기능

3. 목적

  • 객체를 상태 그대로 저장하고, 필요할 때 다시 생성하여 사용하는 것

  • 객체를 메모리, 데이터베이스, 파일로 옮길 때 사용

  • 다른 서버에서 생성한 객체를 받을 수 있음

4. 사용법

(1)

// Serializable 인터페이스를 구현한 HashMap 클래스
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

    private static final long serialVersionUID = 362498820763181265L;
}
// static final long으로 선언해야 하고, 변수명도 serialVersionUID 로 선언해 주어야 자바에서 인식 가능(구현할 때와 동일하게)
static final long serialVersionUID = 1L;

Serializable 인터페이스를 구현한 후에는 위와 같이 serialVersionUID라는 값을 지정해 주는 것을 권장
(별도로 지정하지 않으면, 자바 소스가 컴파일될 때 자동으로 생성)

(2) 예약어 transient

구현 클래스

public class SerialDTO implements Serializable {
    private String booName;
    private int bookOrder;
    private boolean bestSeller;
    private long soldPerDay;

    public SerialDTO(String booName, int bookOrder, boolean bestSeller, long soldPerDay) {
        this.booName = booName;
        this.bookOrder = bookOrder;
        this.bestSeller = bestSeller;
        this.soldPerDay = soldPerDay;
    }

    @Override
    public String toString() {
        return "SerialDTO{" +
                "booName='" + booName + '\'' +
                ", bookOrder=" + bookOrder +
                ", bestSeller=" + bestSeller +
                ", soldPerDay=" + soldPerDay +
                '}';
    }
}

객체 저장 클래스

public class ManageObject {
    public static void main(String[] args) {
        ManageObject manage = new ManageObject();
        String fullPath = "/Users/choejeong-gyun/Documents/test.md";

        SerialDTO dto = new SerialDTO("God of Java", 1, true, 100);
        manage.saveObject(fullPath, dto);
    }

    public void saveObject(String fullPath, SerialDTO dto) {
        FileOutputStream fos = null;
        // ObjectOutputStream 클래스를 사용하면, 객체 저장
        // ObjectInputStream 클래스를 사용하면, 저장해놓은 객체 읽기
        ObjectOutputStream oos = null;		
        
        try {
        	// FileOutputStream 객체(fos)를 만든 후, ObjectOutputStream 의 매개변수로 넘겼음 → 객체 fos 는 파일에 저장됨
            fos = new FileOutputStream(fullPath);
            oos = new ObjectOutputStream(fos);
            // writeObject 를 통해, 매개변수로 넘어온 객체를 저장
            oos.writeObject(dto);
            System.out.println("Write Success");
        } catch (Exception e) { 
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

객체 읽기 클래스

public class ManageObject {
    public static void main(String[] args) {
        ManageObject manage = new ManageObject();
        String fullPath = "/Users/choejeong-gyun/Documents/test.md";
        manage.loadObject(fullPath);
    }

    public void loadObject(String fullPath) {
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        try {
            fis = new FileInputStream(fullPath);
            ois = new ObjectInputStream(fis);
            Object obj = ois.readObject();
            SerialDTO dto = (SerialDTO)obj;
            System.out.println(dto);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
                    ois.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        if (fis != null) {
            try {
                fis.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

파일에 저장된 객체 정보 읽은 결과

SerialDTO{booName='God of Java', bookOrder=1, bestSeller=true, soldPerDay=100}

transient 를 사용한다면?
구현 클래스(SerialDTO)에서 transient 예약어를 추가하면,

transient private int bookOrder;

객체를 생성할 때는 bookOrder에 1을 넣었지만, 결과에는 0이 나오게 된다.

Write Success
SerialDTO{booName='God of Java', bookOrder=0, bestSeller=true, soldPerDay=100}

transient 예약어를 사용하여 선언한 변수는 Serializable 의 대상에서 제외되기 때문이다.

따라서, 보안상 중요한 변수나 꼭 저장해야 할 필요가 없는 변수(패스워드와 같이)에 대해서는 transient를 사용할 수 있다.

(3)

  • java.io.Serializable 인터페이스를 구현한 클래스의 객체만 직렬화될 수 있다.(모든 클래스를 직렬화할 수는 없다)
  • Serializable 인터페이스는 JVM에게 이 클래스가 직렬화될 수 있다는 것을 알려주는 역할
public interface Serializable {
}

참고: 직렬화와 역직렬화 (Serializable)
참고: Basics: 직렬화(Serialization)란? (feat. Java)
참고: [Java] 직렬화(Serialization)란 무엇일까?
참고: JAVA의 객체 직렬화(Serialization)와 JSON 직렬화

profile
개발자로 거듭나기!

0개의 댓글