자바/입출력 IO

직렬화가 가능한 클래스 만들기 - Serializable, transient (2)

백_곰 2022. 4. 29. 11:23

참고) 아래의 예제에서 사용될 UserInfo 클래스는 아래와 같다.

package Serialization;

public class UserInfo implements java.io.Serializable {
	String name;
	String password;
	int age;

	public UserInfo() {
		this("Unknown", "1111", 0);
	}

	public UserInfo(String name, String password, int age) {
		this.name = name;	
		this.password = password;	
		this.age = age;	
	}

	public String toString() {
		return "("+ name + "," + password + "," + age + ")";
	}
}

 

 

 

 

1-1. 직렬화를 이해하기 위한 예제(1)

: 직렬화를 통해 UserInfo.ser를 만들어 객체를 저장후 파일로 출력하는 예제이다.

 

package Serialization;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;

public class Exercise001 {	
	public static void main(String[] args) {
		try {
			String fileName = "UserInfo.ser";
			FileOutputStream     fos = new FileOutputStream(fileName);
			BufferedOutputStream bos = new BufferedOutputStream(fos);

			ObjectOutputStream out = new ObjectOutputStream(bos);
			
			UserInfo u1 = new UserInfo("JavaMan","1234",30);
			UserInfo u2 = new UserInfo("JavaWoman","4321",26);

			ArrayList<UserInfo> list = new ArrayList<>();
			list.add(u1);
			list.add(u2);

			// 객체를 직렬화한다.
			out.writeObject(u1);
			out.writeObject(u2);
			out.writeObject(list);
			out.close();
			System.out.println("직렬화가 잘 끝났습니다.");
		} catch(IOException e) {
			e.printStackTrace();
		}
	}
}

 

( 이 예제만 보면 직렬화는 간단하게 보이지만, 실제로는 상당히 복잡하고 시간도 오래걸린다. )

( ArrayList<UserInfo>만 봐도 저장된 모든 객체들과 각 객체의 인스턴스 변수가 참조하고 있는 객체들까지

모두 직렬화된다. )

 

 

 

 

1-2. 역직렬화를 이해하기 위한 예제(2)

: 1-1 예제(1)에서 했던 직렬화를 다시 역직렬화하는 예제이다.

 

package Serialization;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.ArrayList;

public class Exercise002 {
	public static void main(String[] args) {
		try {
			String fileName = "UserInfo.ser";
			FileInputStream     fis = new FileInputStream(fileName);
			BufferedInputStream bis = new BufferedInputStream(fis);

			ObjectInputStream in = new ObjectInputStream(bis);

			// 객체를 읽을 때는 출력한 순서와 일치해야한다.
			UserInfo u1 = (UserInfo)in.readObject();
			UserInfo u2 = (UserInfo)in.readObject();
			ArrayList list = (ArrayList)in.readObject();

			System.out.println(u1);
			System.out.println(u2);
			System.out.println(list);
			in.close();
		} catch(Exception e) {
			e.printStackTrace();
		}
	} // main
} // class

 

( 주의할 점은 직렬화했던 순서대로 역직렬화해야 한다는 것이다. )

( 그러므로, ArrayList와 같은 컬렉션에 저장해서 직렬화하는 것을 추천한다. )

( 직렬화/역직렬화할 때 ArrayList만 하면 되므로, 순서는 상관 없다. )

 

 

 

 

1-3. 직렬화를 이해하기 위한 예제(3)

: 직렬화되지 않는 조상의 인스턴스 변수를 직렬화할 수 있게 구현한 예제이다.

 

package Serialization;

import java.io.*;

class SuperUserInfo {
	String name;
	String password;

	SuperUserInfo() {
		this("Unknown","1111");
	}

	SuperUserInfo(String name, String password) {
		this.name = name;
		this.password = password;
	}
} // class SuperUserInfo

public class UserInfo2 extends SuperUserInfo implements java.io.Serializable {
	int age;

	public UserInfo2() {
		this("Unknown", "1111", 0);
	}

	public UserInfo2(String name, String password, int age) {
		super(name, password);
		this.age = age;	
	}

	public String toString() {
		return "("+ name + "," + password + "," + age + ")";		
	}

	private void writeObject(ObjectOutputStream out)
		throws IOException {
		out.writeUTF(name);	
		out.writeUTF(password);	
		out.defaultWriteObject();
	}

	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
		name = in.readUTF();
		password = in.readUTF();
		in.defaultReadObject();
	
	}
} // class UserInfo2

 

( name과 password를 위해 writeObject()readObject()를 추가해서 직접 직렬화하도록 구현해야 한다. )

( writeObject()readObject()는 직렬화와 역직렬화 작업시에 자동적으로 호출된다. )

( 접근 제어자는 private으로 단순히 정해진 규칙이다. )

 

( name과 password는 String이기 때문에, writeUTF()와 readUTF()를 사용했다. )

( 이외에도 writeInt()와 readInt() 등이 있기 때문에, 각 인스턴스 변수에 맞는 것을 잘 사용하면 된다. )

 

( 마지막으로 defaultWriteObject()는 인스턴스 변수 age의 직렬화를 수행한다. )

 

 

 

 

2. 직렬화가능한 클래스의 버전관리

- 직렬화된 객체를 역직렬화할 때는 직렬화 했을 때와 같은 클래스를 사용해야 한다.

 

- 그러나, 이름이 같더라도 클래스의 내용이 변경된 경우 역직렬화는 실패한다.

 

- static 변수상수 또는 transient가 붙은 인스턴스 변수가 추가되는 경우에는 직렬화에 영향을

미치지 않기 때문에 클래스의 버전을 다르게 인식하도록 할 필요 없다.

 

 

- 네트웍으로 객체를 직렬화하여 전송하는 경우, 보내는 쪽과 받는 쪽이 모두 같은 버전의 클래스를

가지고 있어야 하는데 클래스가 조금만 변경되어도 해당 클래스를 재배포하는 것은 프로그램 관리를

어렵게 만들어버린다. 

 

- 이럴때, 아래의 코드처럼 클래스의 버전을 수동으로 관리해줄 필요가 있다.

class MyData implements java.io.Serializable{
	static final long serialVersionUID = 3518646845468468889L;
	int value1;
}

 

 

( serialVersionUID를 정의해준다면, 클래스의 내용이 바뀌어도 클래스의 버전이 자동생성된 값으로 변경되지

않는다. )

( serialVersionUID의 값은 정수값이면 어떠한 값으로 지정할 수 있지만, 서로 다른 클래스간에 같은 값을 갖지

않도록 serialver.exe를 사용해서 생성된 값을 사용하는 것이 보통이다. )

( serialver.exe은 cmd에서 자신의 클래스를 seialver 뒤에 써 주면 된다. )

 

> serialver <클래스이름>

 

( serialVersionUID의 값이 정의되어 있다면 그 값을 출력하고, 그렇치 않으면 자동 생성하여 출력한다. )