직렬화가 가능한 클래스 만들기 - Serializable, transient (2)
참고) 아래의 예제에서 사용될 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의 값이 정의되어 있다면 그 값을 출력하고, 그렇치 않으면 자동 생성하여 출력한다. )