본문 바로가기
자바

[Java] 자바의 Serializable

by 책 읽는 개발자_테드 2021. 9. 14.
반응형

학습목표

·  Serializable이란?

·  객체 저장하기

·  객체 읽기

·  serialVersionUID 사용하기

·  transient

 


Serializable이란?

· 구현한 객체를 바이트 스트림으로 변환하여 저장하거나 전송할 수 있도록하는 인터페이스

· 선언된 변수와 메소드가 없음

· 용도: 생성한 객체를 파일로 저장, 저장한 객체 읽기, 객체를 다른 서버로 전송, 다른 서버에서 생성한 객체 읽기

https://kim6394.tistory.com/257

 

java-io 패키지에 선언된 Serializable 인터페이스

package java.io;
public interface Serializable {
}

 

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

   - serialVersionUID: 해당 객체의 버전을 명시하는 데 사용

   - 반드시 static final long으로 선언

 

객체 저장하기

· 자바에서는 ObjectOutputStream 클래스를 사용하여 객체 저장 

public class SerialDTO implements Serializable {
    private String bookName;
    private int bookOrder;
    private boolean bestSeller;
    private long soldPerDay;
    public SerialDTO(String bookName, int bookOrder, boolean bestSeller, long soldPerDay){
        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 +
                '}';
    }
}

 

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import static java.io.File.separator;

public class SerializableDemo {
    public static void main(String[] args){
        //저장될 경로 + 파일이름 설정
        String fullPath = separator + "Users" + separator + "ted.sc" + separator + "Desktop" + separator + "git" +
                separator + "javaStudy" + separator + "src" +separator + "SerializableAndNio" + separator + "serial.obj";
        SerialDTO dto = new SerialDTO("GodOfJavaBook", 1, true, 100);
        saveObject(fullPath, dto);
    }

    static void saveObject(String fullPath, SerialDTO dto){
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        try{
            fos = new FileOutputStream(fullPath); //FileOutputStream 객체 생성
            oos = new ObjectOutputStream(fos);    //ObjectOutputStream 객체를 생성하고, 이때 fos 객체를 매개변수로 넘김. 이렇게 하면 해당 객체가 파일에 저장.
            oos.writeObject(dto); // 메서드의 매개변수로 넘어온 dto 객체 저장
        }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();
            }
        }
    }
}

· 위 코드를 실행하면, 다음과 같이 객체가 파일로 저장된다.

· 해당 객체는 바이너리로 저장

· 이때 SerialDTO 클래스에서 Serializable 인터페이스를 implements 하지 않으면, NotSerializableException이 발생

 

객체 읽기

· 자바에서는 ObjectInputStream 클래스를 사용하여 객체를 읽음 

public class SerializableDemo {
    public static void main(String[] args){
        String fullPath = separator + "Users" + separator + "ted.sc" + separator + "Desktop" + separator + "git" +
                separator + "javaStudy" + separator + "src" +separator + "SerializableAndNio" + separator + "serial.obj";
        loadObject(fullPath);
    }

    static 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에 구현한 toString() 메서드로 인해 읽어 들인 객체의 내용이 출력된다.

 

serialVersionUID 사용하기

· 이때 Serializable 객체를 변경하여 저장했던 객체와 다르게 만들면 오류가 발생한다.

public class SerialDTO implements Serializable {
    private String bookType="IT";
    
    //이후 생략

· 객체가 변경되어 컴파일시 serialVersionUID가 다시 생성되어 InvalidClassException 발생

java.io.InvalidClassException: SerializableAndNio.SerialDTO; local class incompatible: 
stream classdesc serialVersionUID = -2328603707165096545, local class serialVersionUID = 8853932105027462604
	at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:722)
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2022)
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1872)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2179)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1689)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:495)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:453)
	at SerializableAndNio.SerializableDemo.loadObject(SerializableDemo.java:22)
	at SerializableAndNio.SerializableDemo.main(SerializableDemo.java:13)

· SerialVersionUID를 사용하면, 객체가 수정되었을 때 이것과 다르게 작동한다.

· SerialDTO에 bookType 변수를 제거하고, serialVersionUID을 선언한 후 객체를 다시 파일로 저장한다.

public class SerialDTO implements Serializable {
    static final long serialVersionUID=1L;
    private String bookName;
    private int bookOrder;
    private boolean bestSeller;
    private long soldPerDay;
    public SerialDTO(String bookName, int bookOrder, boolean bestSeller, long soldPerDay){
        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 +
                '}';
    }
}

 

· 객체를 파일로 저장했다면 bookType 변수를 다시 추가하고, 해당 변수를 위해 toString() 메서드도 추가한다.

 

public class SerialDTO implements Serializable {
    static final long serialVersionUID=1L;
    private String bookType="IT";
    private String bookName;
    private int bookOrder;
    private boolean bestSeller;
    private long soldPerDay;
    public SerialDTO(String bookName, int bookOrder, boolean bestSeller, long soldPerDay){
        this.bookName = bookName;
        this.bookOrder = bookOrder;
        this.bestSeller = bestSeller;
        this.soldPerDay = soldPerDay;
    }

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

 

· 파일을 읽어들이면, 다음과 같이 InvalidClassException이 발생하지 않고 변경되어 인식 할 수 없는 부분(bookType)이 null로 처리된다.

· serializable한 객체 내용이 바뀌었는데 아무런 예외가 발생하지 않으므로 운영 상황에서 데이터가 꼬일 수 있으므로 권장 x

· 데이터가 바뀌면 serialVersionUID를 변경하는 습관을 갖기

transient

· 선언한 변수를 Serializable 대상에서 제외하는 예약어

· 용도: 보안상 중요한 변수, 꼭 저장할 필요가 없는 변수에 사용

ex) 패스워드

 

▶ SerialDTO의 boorOrder 변수 앞에 transient 선언

public class SerialDTO implements Serializable {
    static final long serialVersionUID=1L;
    private String bookName;
    transient private int bookOrder;
    private boolean bestSeller;
    private long soldPerDay;
    public SerialDTO(String bookName, int bookOrder, boolean bestSeller, long soldPerDay){
        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를 변경하고, 객체를 읽으면 bookOrder가 Serializable 대상에서 제외되어 값을 1로 지정했지만 0이 출력된다.

 

반응형

댓글