객체 직렬화 :
- 객체를 바이트로 저장하는 기술.
객체를 바이트로 변환하는 것을 직렬화라 하고 다시 객체로 복원하는 작업을 역직렬화
라고 한다.
약속된 규칙에 의해서 객체 메모리를 한 줄로 늘어선 바이트의 형태로 만드는 것.
직렬화의 이유:
- 객체가 생성되어 적재되는 메모리는 순간적이기 때문에 영구적으로 보관하기 위해
직렬화를 사용한다.
직렬화의 형태:
- 직렬화을 할 클래스는 Serializable 인터페이스를 구현(implements)하여 주면 된다.
그리고 클래스의 객체를 생성해주고 생성된 객체를 파일 스트림 혹은 네트워크 스트림
을 통해서 Output 시켜주면 다른 클래스에서 출력된 객체를 가져와서 역직렬화를 통해
객체로 변환하여 사용할 수 있다
자..인제 간단한 예제를 통해서 직렬화와 역직렬화의 과정을 훔쳐보자구..
먼저 직렬화할 클래스를 생성해보자.
import java.io.*;
public class SerialObject
implements Serializable{
// 객체 직렬화를 위한 Serializable 인터페이스 상속
private String name; //이름
private String dept; //부서
private String title; //직채
public SerialObject(String name, String dept, String title){
//Constructor
this.name = name;
this.dept = dept;
this.title = title;
}
public String toString(){
return name +" : "+ dept +" : "+ title;
}
}
// 일반 클래스와 다른 점은 오직 객체직렬화를 위한 인터페이스를
// 상속 받았다는 점 밖에 없다.
import java.io.*;
public class SerialObjectTest{
public static void main(String[] args)throws Exception{
FileOutputStream fos = new FileOutputStream("serialObj.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
//// 직렬화가 가능한 SerialObject 객체 생성.
SerialObject so1 = new SerialObject("박찬호", "LA", "투수");
SerialObject so2 = new SerialObject("이승엽", "요미우리", "4번타자");
SerialObject so3 = new SerialObject("이병규", "주니치", "3번타자");
// ObjectOutpStream의 writeObject()를 통해
// 총 3개의 객체를 파일(serialObj.txt)로 출력시켜준다.
oos.writeObject(so1);
oos.writeObject(so2);
oos.writeObject(so3);
//스트림을 닫아준다.
oos.close();
// 다시 역직렬화를 통해 객체를 파일로부터 불러들여와서
// 객체를 복원시켜보자.FileInputStream fis = new FileInputStream("serialObj.txt");
// 파일로 출력되어 있는 객체를 입력받기 위한 파일 스트림 생성
ObjectInputStream ois = new ObjectInputStream(fis);
// 객체를 입력받기 위한 객체 입력 스트림 생성.
// 객체를 생성하여 ObjectInputStream의 readObject()를 통해
// 객체를 입력받아 역직렬화 해준다.
SerialObject rso1 = (SerialObject)ois.readObject();
SerialObject rso2 = (SerialObject)ois.readObject();
SerialObject rso3 = (SerialObject)ois.readObject();
// 입력받은 객체 출력..
System.out.println(rso1);
System.out.println(rso2);
System.out.println(rso3);
}
}
-실행 결과
와우...파일에서 객체를 가져와서 그 정보를 출력해줬다...
이거 이거 잼있다..후훗...
참!! 그리고 toString() 메소드를 사용하지 않았는데도 toString()를 호출한 효과와 같은 이유는 뭔지 잊지 말자구!!! 기본적으로 print()메소드에서 객체를 호출할경우 객체의 toString 메소드를 호출하기때문에 오버라이딩 해준 toString()메소드가 호출되는 거라구!!
Externalizable( 인터페이스끼리 상속)
- 이 인터페이스는 더 큰 인테페이스를 만들기 위해 Serializable 인터페이스를 상속(Extends) 받는다.
- 객체 직렬화를 위한 Externalizable 인터페이스.
- 사용자가 직접 객체 직렬화를 위한 구현을 해야한다.
- method
writeExternal() : 기록부분 제어 throws IOException처리를 해줘야한다
readExternal() : 입력부분 제어throws IOException, ClassNotFoundException 처리를
해줘야한다
자~Serializable을 통한 객체 직렬화를 해봤으니 이제 Externalizable을 통한 객체 직렬화를 해보자.
먼저 직렬화할 클래스를 만들어보자.
import java.io.*;
class ExternalObject implements Externalizable{
private int id; //부서
private String name; //이름
private float height;//신장
//****************************Constructor
public ExternalObject(){};
//매개 변수가 없는 default 생성자를 꼭 만들어줘야함.
public ExternalObject(int id, String name, float height)
{
this.id = id;
this.name = name;
this.height = height;
}
//****************************Constructor end.
public void readExternal(ObjectInput oi)
throws IOException,ClassNotFoundException
//readExternal() 메소드를 꼭 구현해줘야하며
//IOException과 ClassNotFoundException 예외처리를 꼭해줘야한다.
//또한 변수의 순서도 꼭 맞춰줘야한다.
{
System.out.println("readExteranl() Call");
id = oi.readInt();
name = (String)oi.readObject();
height = oi.readFloat();
}
public void writeExternal(ObjectOutput oo)
throws IOException
//readExternal()과 마찬가지로 꼭 구현해줘야하는 메소드이며
//IOException처리를 해줘야한다.
{
System.out.println("writeExternal() Call");
oo.writeInt(id);
oo.writeObject(name);
oo.writeFloat(height);
}
public String toString()
{
return id+ ":"+ name +":"+ height;
}
}
객체직렬할 클래스를 만들었으니 Test해보자.
import java.io.*;
public class ExteranlTest{
public static void main(String[] args)throws IOException, ClassNotFoundException{
//먼저 External을 통해 객체 직렬화를 가능케 한 클래스
//ExternalObject를 생성한다.
ExternalObject so1 = new ExternalObject(1,"박찬호", 180.24f);
ExternalObject so2 = new ExternalObject(2,"이승엽", 190.13f);
ExternalObject so3 = new ExternalObject(3,"이병규", 186.64f);
//파일출력스티림과 객체 출력 스트림을 생성하여
//생성된 객체를 파일로 출력시켜준다.
FileOutputStream fos = new FileOutputStream("objFile.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
//객체출력 스트림을 통해 객체를 파일(objFile.dat)로 출력시켜준다.
oos.writeObject(so1);
oos.writeObject(so2);
oos.writeObject(so3);
//스트림을 닫아준다.
oos.close();
//객체 입력을 위한 파일, 객체 입력 스트림 생성.
FileInputStream fis = new FileInputStream("objFile.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
//객체 입력 스트림의 readObject()를 통해 객체를 가져온다.
ExternalObject exObj1 = (ExternalObject)ois.readObject();
ExternalObject exObj2 = (ExternalObject)ois.readObject();
ExternalObject exObj3 = (ExternalObject)ois.readObject();
//스트림을 닫아준다.
//객체 출력.
System.out.println(exObj1);
System.out.println(exObj2);
System.out.println(exObj3);
ois.close();
}
}
- 실행결과 -
멋지다~
객체 직렬화를 해주는 두가지 방법에 대해 보았다.
근데 왜 자바는 같은 기능의 클래스(엄연히 따지자면 인터페이스)를 두가지나 제공하여 주는 것일까?
객체 직렬화를 위한 인터페이스 두가지의 차이점은 바로 프로그래머가 데이터의 저장과 복구를 제어할 수 있냐는 것이다. 적, Externalizable의 경우 Serializable 인터페이스보다 미세한 직렬화를 다루기 위해서 사용하는 것이다!~!!!
객체를 직렬화한다는건...별다르게 어떤 메소드를 실행시킨다거나 하는게 아니라.
단지 Serializable이나 Externalizable 인터페이스를 상속시켜주면 되는 것이었다..
예전에도 노트한적이 있지만 인터페이스를 구현(implements)한다는 것은 결국 객체를 어떤 구조로 메모리에 적재할까에 대한 문제를 결정하는 것이라고 생각한 적이 있었다.
이 예제에서도 보면 알 수 있듯이 직렬화된 클래스가 직접하는 것은 아무것도 없다.
단지 ObjectIOStream에서 출력하고 입력하려는 객체가 직렬화가 되어 있어야 한다는 것이다.
그렇다면 내가 생각한 implements의 개념이 맞는것 같다.
음...그런데.....그렇게 생각하면...안될거 같기도 한게..
Exteranlizable의 경우는 ... 메소드를 직접 구현해주게 되는데...
그 말인 즉슨...Object IO Stream 에서 readObject나 writeObject를 호출할 경우 Externalizable의 writeExternal이나 readExternal 메소드를 호출한다는 거 아닌가?..
그러니깐 우리가 구현 해준 메소드들을 호출하게 되는거자나.....
그 말인 즉슨...인터페이스를 implement하는 순간...
클래스에 메모리 구조만 건드는것이 아니라...다른 일도 한다는 건가?..
아니면...내가 생각한 것이 완전 틀린 것일까...........
이 부분은 내일 선생님 한테 물어봐서 확실히 해야겠다......
Transient 예약어
- 직렬화에 굳이 필요 없는 멤버 변수를 직렬화에서 제외시켜주는 예약어
- 멤버 변수 앞에 transient를 명시함으로써 사용.
EX) private transient String title;
- transient 하여준 객체는 Null값을 갖게 된다.
- static 변수는 기본적으로 transient 라고 생각할 수 있다.
직렬화시엔 자동적으로 static 변수는 제외된다.
직렬화의 조건
- 직렬화시킬 클래스의 멤버필드는 non-static, 그리고 non-transient 가 있어서는 안된다.