직렬화란?
직렬화(serialization)란 객체를 데이터 스트림으로 만드는 것을 뜻한다. 다시 얘기하면 객체에 저장된 데이터를 스트림에 쓰기(write)위해 연속적인(serial) 데이터로변환하는 것을 말한다.
반대로 스트림으로부터 데이터를 읽어서 객체를 만드는 것을 역직렬화(deserialization)라고 한다.
직렬화라는 용어 때문에 괜히 어렵게 느껴질 수 있는데 사실 객체를 저장하거나 전송하려면 당연히 이렇게 할 수 밖에 없다.
이미 앞서 객체에 대해서 설명했지만, 여기서 객체란 무엇이며, 객체를 저장한다는 것은 무엇을 의미하는가에 대해서 다시 한 번 정리하고 넘어가는 것이 좋을 것 같다.
객체는 클래스에 정의된 인스턴스변수의 집합이다. 객체에는 클래스변수나 메서드가 포함되지 않는다. 객체는 오직 인스턴스변수들로만 구성되어 있다.
이전에는 이해를 돕기 위해 객체를 생성하면 인스턴스변수와 메서드를 함께 그리곤 했지만 사실 객체에는 메서드가 포함되지 않는다. 인스턴스변수는 인스턴스마다 다른 값을 가질 수 있어야하기 때문에 별도의 메모리공간이 필요하지만 메서드는 변하는 것이 아니라서 메모리를 낭비해 가면서 인스턴스마다 같은 내용의 코드(메서드)를 포함시킬 이유는 없다.
그래서 객체를 저장한다는 것은 바로 객체의 모든 인스턴스변수의 값을 저장한다는 것과 같은 의미이다. 어떤 객체를 저장하고자 한다면, 현재 객체의 모든 인스턴스변수의 값을 저장하기만 하면 된다. 그리고 저장했던 객체를 다시 생성하려면, 객체를 생성한 후에 저장했던 값을 읽어서 생성한 객체의 인스턴스변수에 저장하면 되는 것이다.
클래스에 정의된 인스턴스변수가 단순히 기본형일 때는 인스턴스변수의 값을 저장하는 일이 간단하지만 인스턴스변수의 타입이 참조형일 때는 그렇게 간단하지 않다. 예를 들어 인스턴스변수의 타입이 배열이라면 배열에 저장된 값들도 모두 저장되어야할 것이다.
그러나 우리는 객체를 어떻게 직렬화해야 하는지 전혀 고민하지 않아도 된다. 다만 객체를 직렬화/역직렬화 할 수 있는 ObjectInputStream과 ObjectOutputStream을 사용하는 방법만 알면 된다.
|참고| 두 객체가 동일한지 판단하는 기준이 두 객체의 인스턴스변수의 값들이 같고 다름이라는 것을 상기하자.
ObjectInputStream과 ObjectOutputStream
직렬화(스트림에 객체를 출력)에는 ObjectOutputStream을 사용하고 역직렬화(스트림으로부터 객체를 입력)에는 ObjectInputStream을 사용한다.
ObjectInputStream과 ObjectOutputStreamdms 각각 InputStream과 OutputStream을 직접 상속받지만 기반스트림을 필요로 하는 보조스트림이다. 그래서 객체를 생성할 때 입출력(직렬화/역직렬화)할 스트림을 지정해주어야 한다.
ObjectInputStream(InputStream in)
ObjectOutputStream(OutputStream out)
만일 파일에 객체를 저장(직렬화)하고 싶다면 다음과 같이 하면 된다.
FileOutputStream fos = new FileOutputStream("objectfile.ser");
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject(new UserInfo());
위 코드는 objectfile.ser 이라는 파일에 UserInfo 객체를 직렬화하여 저장한다. 출력할 스트림(FileOutputStream)을 생성해서 이를 기반스트림으로 하는 ObjectOutputStream을 생성한다.
ObjectOutputStream의 writeObject(Object obj)를 사용해서 객체를 출력하면, 객체가 파일에 직렬화되어 저장된다.
역직렬화 방법 역시 간단하다. 직렬화할 때와는 달리 입력스트림을 사용하고 writeObject(Object obj) 대신 readObject()를 사용하여 저장된 데이터를 읽기만 하면 객체로 역직렬화 된다.
다만, readObject()의 반환타입이 Object이기 때문에 객체 원래의 타입으로 형변환 해주어야 한다.
FileInputStream = fis = new FileInputStream("objectfile.ser");
ObjectInputStream in = new ObjectInputStream(fis);
UserInfo info = (UserInfo) in.readObject();
ObjectInputStream과 ObjectOutputStream에는 readObject()와 writeObject()이외에도 여러가지 타입의 값을 출력할 수 있는 메서드를 제공한다.
ObjectInputStream | ObjectOutputStream |
void defaultReadObject() int read() int read(byte[] buf, int off, int len) boolean readBoolean() byte readByte() char readChar() double readDouble() float readFloat() int readInt() long readLong() short readShort() Object readObject() int readUnsignedByte() int readUnsignedShort() Object readUnshared() String readUTF() |
void defaultWriteObject() void write(byte[] buf) void write(byte[] buf, int off, int len) void write(int val) void writeBoolean(boolean val) void wrtie(int val) void writeBytes(String str) void writeChar(int val) void writeChars(String str) void writeDouble(double val) void writeFloat(float val) void writeInt(int val) void writeLong(long val) void writeObject(Object obj) void writeShort(int val) void writeUnshared(Object obj) void writeUTF(String str) |
이 메서드들은 직렬화와 역직렬화를직접 구현할 때 주로 사용되며, defaultReadObject()와 defualtWriteObject()는 자동 직렬화를 수행한다.
객체를 직렬화/역직렬화 하는 작업은 객체의 모든 인스턴스변수가 참조하고 있는 모든 객체에 대한 것이기 때문에 상당히 복잡하며 시간도 오래 걸린다. readObject()와 writeObject()를 사용한 자동 직렬화가 편리하기는 하지만 직렬화작업시간을 단출시키려면 직렬화하고자 하는 객체의 클래스에 추가적으로 다음과 같은 2개의 메서드를 직접 구현해 주어야 한다.
Private void writeObject(ObjectOutputStrema out)
throws IOException {
//write 메서드를 사용해서 직렬화를 수행한다.
}
Private void readObject(ObjectInputStream in)
throws IOException, ClassNoutFoundException {
// read 메서드를 사용해서 역직렬화를 수행한다.
}
'Java 길찾기 > Java의 정석' 카테고리의 다른 글
[Java] 직렬화 가능한 클래스의 버전관리 (0) | 2022.08.03 |
---|---|
[Java] 직렬화가 가능한 클래스 만들기 - Serializable, transient (0) | 2022.08.02 |
[Java] 입출력 I/O - File (0) | 2022.07.29 |
[Java] 입출력 I/O - RandomAccessFile (0) | 2022.07.28 |
[Java] 입출력 I/O - 표준입출력의 대상변경(setOut(), setErr(), setIn()) (0) | 2022.07.27 |