Java

Serializable

jamie. 2020. 2. 7. 09:35
반응형

Serializable

java.io 패키지의 Serializable Interface

선언된 변수나 메서드가 없음

클래스가 파일에 읽거나 쓸 수 있도록 하거나, 다른 서버로 보내거나 받을 수 있도록 하려면 해당 인터페이스를 구현(implements)해야 함 - 구현시 JVM에서 해당 객체를 저장하거나 다른 서버로 전송할 수 있도록 해줌

serialVersionUID

Serializable 인터페이스를 구현한 후, serialVersionUID 값을 지정해주는 것을 권장

별도로 지정하지 않으면, 컴파일시 자동 생성 됨

static final long serialVersionUID = 1L;

뒤에 지정하는 값 : 해당 객체의 버전을 명시하는 데 사용됨

: 해당 객체가 같은지 다른지를 확인할 수 있도록 관리하는 값

단, 같은 UID일지라도 변수의 개수나 타입 등이 다르면 다른 클래스로 인식함 유의

ObjectOutputStream & ObjectInputStream

ObjectOutputStream : 자바에서 해당 클래스를 사용시 객체를 저장할 수 있음

ObjectInputStream : 자바에서 해당 클래스를 사용하면 저장해 놓은 객체를 읽을 수 있음

객체 저장

import java.io.FileOutputStream;
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 = "/Users/hs" + separator + "chicken.obj";
        SerialDTO dto = new SerialDTO("ABChicken", 1, true, 100);
        manager.saveObject(fullPath, dto);
    }

    public void saveObject(String fullPath, SerialDTO dto) {
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        try {
            // 파일 객체 생성
            fos = new FileOutputStream(fullPath);
            // ObjectOutputStream 객체, 생성시 파일객체를 넘김 > 파일에 저장됨
            oos = new ObjectOutputStream(fos);
            // 매개 변수로 넘어온 객체 저장
            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();
                }
            }
        }
    }
}
import java.io.Serializable;

public class SerialDTO implements Serializable {
    private String chickenName;
    private int chickenOrder;
    private boolean bestChicken;
    private long soldPerDay;
    public SerialDTO(String chickenName, int chickenOrder, boolean bestChicken, long soldPerDay) {
        super();
        this.chickenName = chickenName;
        this.chickenOrder = chickenOrder;
        this.bestChicken = bestChicken;
        this.soldPerDay = soldPerDay;
    }

    @Override
    public String toString() {
        return "SerialDTO [chickenName = " + chickenName + ", chickenOrder = " + chickenOrder
                + ", bestChicken = " + bestChicken + ", soldPerDay = " + soldPerDay + "]";
    }
}

// Serializable 구현하지 않을 경우
java.io.NotSerializableException: SerialDTO
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at ManageObject.saveObject(ManageObject.java:23)
	at ManageObject.main(ManageObject.java:11)

// 구현한 경우 - 파일에 들어감 (바이너리 형식..)
$  cat /Users/hs/chicken.obj 
??sr	SerialDTOJ,?>?Eo?Z
                          bestChickenI
                                      chickenOrderJ
soldPerDayL
           chickenNametLjava/lang/String;xpdt	ABChicken

객체 읽기

import java.io.*;

import static java.io.File.separator;

public class ManageObject {
    public static void main(String[] args) {
        ManageObject manager = new ManageObject();
        String fullPath = "/Users/hs" + separator + "chicken.obj";
        manager.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 [chickenName = ABChicken, chickenOrder = 1, bestChicken = true, soldPerDay = 100]

BUILD SUCCESSFUL in 0s

Serializable 객체의 형태가 변경되면

컴파일시 serialVersionUID가 재생성됨

즉, Serializable을 구현한 클래스에 클래스 변수를 추가한다면, serialVersionUID가 변경되므로 읽을 수 없게됨

// SerialDTO에 클래스 변수가 추가된다면 - 객체읽을경우
//     private String chickenType = "A";
java.io.InvalidClassException: SerialDTO; local class incompatible: stream classdesc serialVersionUID = 5344876925460770704, local class serialVersionUID = -8689217447952664982
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at ManageObject.loadObject(ManageObject.java:18)
	at ManageObject.main(ManageObject.java:9)

그럴경우 serialVersionUID를 명시해주기

static final long serialVersionUID = 1L;

serialVersionUID를 지정해놓은 상태에서 저장되어 있는 객체와 읽는 객체가 다르면?

SerialDTO [chickenName=ABChicken, chickenOrder=2, bestChicken=true, soldPerDay=200, ChickenTypes=null]
  • 변수의 이름이 바뀌거나 추가되는 등 하면, 객체에서 찾을 수 없으면 null로 처리

  • 예외는 발생하지 않음

  • Serializable한 객체의 내용이 바뀌었는데도 예외가 발생하지 않으면 데이터가 꼬일 수 있음 = 권장하지 않음

  • 데이터가 변경되면 serialVersionUID를 변경하는 습관을 가질 것 > 자동으로 해주는 IDE도 많음

transient 예약어

보안상 중요한 변수나 꼭 저장할 필요가 없는 변수에 대해 사용하는 예약어

객체를 저장하거나 타 JVM으로 보낼 때, transient라는 예약어를 사용하여 선언한 변수는 Serializable 대상에서 제외됨 (아예 무시 됨)

해당 객체를 생성한 JVM에서 사용할 때는 문제 없음

패스워드 등에서 사용 

 

반응형