본문 바로가기
Language/Java

[Java]Serializable(직렬화)란?

by jaee_ 2021. 9. 5.

Serializable(직렬화)란?

  • 직렬화(Serialization)란 자바 시스템 내에서 사용하는 객체 또는 데이터를 자바시스템 외에서도 사용할 수 있도록 Byte 형태로 데이터를 변환하는 기술이다.
  • Byte로 변환된 데이터를 다시 자바의 객체로 변환하는 기술을 역직렬화(Deserialization)라고 한다.

말로 풀어서 설명하면 Serializable란 컴퓨터의 메모리 상에 존재하는 데이터를 파일로서 저장하거나, 통신하는 다른 컴퓨터에게 알맞은 형식으로 맞추어 전달하기 위해 byte Stream형태로 만드는 것을 의미한다.

 

프로그램에서 사용되는 데이터들은 연속적으로 위치해 있지 않고 내부적으로 포인터에 의해 참조되고 있는데, 이는 실행중인 컴퓨터에서만 인식이 가능하기 때문에 다른 컴퓨터와 통신하기 위해선 이렇게 흩뿌려져 있는 데이터를 한 곳으로 모아 포인터가 존재하지 않는 일련의 바이트 형태로 만들어 보내야 하는데 이를 직렬화라고 부른다. 

 

Serializable는 언제 사용하는가?

  • 생성한 객체를 파일로 저장 & 저장한 객체를 읽을 때
  • 객체를 다른 서버로 보낼 때 & 다른 서버에서 생성한 객체를 받을 때

위의 경우 필요한 것이 Serializable 이다. 만약 내가 만든 클래스가 파일에 읽거나 쓸 수 있도록 하거나, 다른서버로 보내거나 받을 수 있도록 하려면 반드시 Serializable 인터페이스를 구현해야 한다.

 

Serializabl 인터페이스를 구현하면 JVM에서 해당 객체는 저장하거나 다른 서버로 전송할 수 있게끔 한다. 

 

아래의 코드를 보자.

import java.io.Serializable;

public class SerialDTO implements Serializable {

    static final long serialVersionUID = 1L;	// (1)

    private String bookName;
    private int bookOrder;
    private boolean bestSeller;
    private long soldPerDay;

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

    @Override
    public String toString() {
        return "SerialDTO [bookName = " + bookName + ", bookOrder = " + bookOrder
            + ", bestSeller = " + bestSeller + " , soldPerDay" + soldPerDay+"]";
    }
}

위의 코드와 같이 직렬화하고자 하는 클래스에 Serializable를 구현하면 자바에서 직렬화를 구현할 수 있다.

 

Serializable을 구현했다면 주의해야할 점이 있는데 SerialVersionUID이다. SerialVersionUID은 직렬화 버전의 고유값으로 각 서버가 쉽게 해당 객체가 같은지 다른지를 확인하기 위한 것 사용된다. 이는 직렬화를 구현할 때 지정해주는 것을 권장한다. 지정하지 않는다고 직렬화가 불가능하진 않다. 자바에서 소스 컴파일 시 지정되어 있지않으면 자동으로 SerialVersionUID의 값을 만들어준다. SerialVersionUID를 지정해주지 않았을 경우엔 어떤 문제가 있길래 지정하는 것을 권장하는 것일까? 

 

예를들어, 위의 SerialDTO클래스에서 SerialVersionUID를 지정해주지 않았다고 가정해보자. 만약 A서버에서 B서버로 SerialDTO 라는 클래스의 객체를 전송한다 했을 때, 보내는 서버 A와 받는 서버 B는 둘다 SerialDTO라는 객체를 가지고 있어야 한다. 하지만 A의 SerialDTO에 type이라는 변수하나를 더 추가하게 되면 소스 컴파일 시 새로운 SerialVersionUID 값이 세팅되며 B 서버에서는 A서버는 SerialVersionUID가 다르니 다른 객체라고 인식할 것이다.

 

이러한 경우를 대비하기 위해선 (1)이라고 주석처리되어 있는 것처럼 SerialVersionUID를 지정해주는 것이 좋다. 

 

 

transient 예약어

transient 예약어를 Serializable를 구현한 클래스 내의 변수에 선언하면 Serializable의 대상에서 제외된다. 패스워드와 같은 보안상 전달되면 안되는 경우 또는 전달할 필요가 없는 변수에 대해서 transient 예약어를 대상에서 제외시킨다. 

 

Serializable를 이용하여 객체 저장

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

import static java.io.File.separator;

public class ManageObject {

    public static void main(String[] args) {
        ManageObject manager = new ManageObject();
        String fullPath = "C:"+separator + "godofjava" + separator + "text" + separator + "serial.obj";
        SerialDTO dto = new SerialDTO("GodOfJava", 1, true, 100);
        manager.saveObject(fullPath, dto);
    }

    private void saveObject(String fullPath, SerialDTO dto) {

        FileOutputStream fos = null;
        ObjectOutputStream oos = null;

        try {
            fos = new FileOutputStream(fullPath);
            oos = new ObjectOutputStream(fos);
            oos.writeObject(dto);
            System.out.println("쓰기 성공");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Serializable를 이용하여 객체 읽기

package study.week05.fileIO;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import static java.io.File.separator;

public class ManageObject {

    public static void main(String[] args) {
        ManageObject manager = new ManageObject();
        String fullPath = "C:"+separator + "godofjava" + separator + "text" + separator + "serial.obj";
        SerialDTO dto = new SerialDTO("GodOfJava", 1, true, 100);
        manager.loadObject(fullPath);
    }

    // 읽기
    private 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 (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

// SerialDTO [bookName = GodOfJava, bookOrder = 1, bestSeller = true , soldPerDay100]

transient 예약어 사용해보기

위에서 작성했던 SerialDTO 클래스의 bookName 변수에 transient 를 추가한 뒤 객체 읽기 메소드를 다시 실행해보자.

 

import java.io.Serializable;

public class SerialDTO implements Serializable {

    static final long serialVersionUID = 1L;

    transient private String bookName; // 추가한 부분
    private int bookOrder;
    private boolean bestSeller;
    private long soldPerDay;

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

    @Override
    public String toString() {
        return "SerialDTO [bookName = " + bookName + ", bookOrder = " + bookOrder
            + ", bestSeller = " + bestSeller + " , soldPerDay" + soldPerDay+"]";
    }
}

결과 

SerialDTO [bookName = null, bookOrder = 1, bestSeller = true , soldPerDay100] 

이처럼 bookName은 null 형태로 전송이 되지 않는 것을 확인 할 수 있다. 

 

댓글