이 글의 문맥을 분석하여 이글루스에 있는 많은 글 중에서 관련성이 높은 글을 자동으로 검색해 낸 결과입니다.
제갈장비
dojeun.egloos.com
이글루스 | 로그인

라면 한그릇 드세요.. ㅋㅋ
by 제갈장비
카테고리
전체
제갈장비-Linux
제갈장비-TOMCAT
제갈장비-JAVA
--------------------
문서-Linux
문서-Powerbuilder
문서-JAVA
문서-ASP
문서-하드웨어
문서-EDPS
문서-MSSQL
--------------------
TIP-JAVA
TIP-Perl
TIP-ASP
TIP-Powerbuilder 01
TIP-Javascript
TIP-Excel
TIP-XML
TIP-MSAccess
TIP-PostgreSql
TIP-Windows
TIP-Linux
TIP-HTML
TIP-TOMCAT
--------------------
ETC
Secret
Util-Japan
미분류
최근 등록된 덧글
잘읽어 보았습니다. 그런..
by 이윤철 at 11/07
1000 666
by qhrlfeh77 at 05/21
123
by qhrlfeh77 at 05/21

by qhrlfeh77 at 05/21
감사합니다!! HashSet..
by 세레네이 at 04/06
찾고 있었던 자료인데 ..
by 나그네 at 01/09
감사합니다. 잘 보았습..
by 졸면죽는다 at 10/28
좋은 정보 정말 감사합니..
by 강성훈 at 10/22
좋은글 퍼가겠습니다.
by 정의진 at 09/29
이제야 봤습니다. iText..
by 제갈장비 at 08/09
이글루 파인더
Powered by egloos
rss

skin by 이글루스
2006년 09월 06일
객체 직렬화(Serialization)와 통신/지속성(Persistency)
객체 직렬화(Serialization)와 통신/지속성(Persistency)

네트웍 상의 통신은 결국 바이트 코드들의 이동이므로, 객체가 네트웍을 오가려면 일단 바이트 코드로 변환될 수 있어야 한다. 그리고, 바이트 코드화된 객체를 받는 프로그램 쪽에서는 그것을 다시 객체로 변환할 수 있어야 한다

객체의 상태를 파일이나 데이타베이스 같은 지속성 장치에 저장하기 위해서는 객체가 또한 바이트 코드로 변환되어야 한다. 그리고, 다시 그것을 읽어들이는 프로그램에서는 그것으로 객체로 변환할 수 있어야 한다

C/C++ 프로그래밍을 하던 사람들은 원래 구조체나 클래스 디자인을 많이 해 보았을 것이기 때문에, 자바의 클래스와 그 객체에 대해 이해하는 것은 어렵지 않으나, 이 객체를 파일이나 기타 출력 장치로 저장하거나 읽어들이는 것에 대해서는 혼동을 많이 한다.

"자바 프로그램은 외부 소스나 출력 장치와 객체를 주고 받을 때, 객체를 스트림 형식으로 주고 받는다"

즉, 다음과 같은 변환 과정을 거쳐야 한다

Program-> Serialize -> File/Network..-> Deserialize -> Program
Object-> Byte Stream-> Object

그렇다면 객체를 어떻게 직렬화하나

모든 자바의 객체를 직렬화시킬 수 있는 것은 아니다. 직렬화 대상이 되는 클래스는 반드시 java.io.Serializable인터페이스를 구현해야 한다. 즉 다음과 같은 형식의 클래스 선언이 이루어져야 한다

... class SerializableClassName extends ... implements java.io.Serializable

물론, java.io.Serializable인터페이스를 구현했다고 해서, 부가적인 작업이 필요한 것은 아니다. 왜냐하면, 이 인터페이스는 아무런 메쏘드도 가지고 있지 않기 때문이다. 엄밀히 말한 것은 아니지만, 빈 인터페이스인데 그저 이 클래스가 직렬화 가능하다는 플래그를 세팅하는 정도의 역할을 하는 것이다. 구현 측면보다는 디자인 측면에서 더 의의가 있겠다

객체 직렬화는 java.io.ObjectInputStream과 java.io.ObjectOutputStream클래스에 의해 이루어진다. 이들 클래스는 매우 중요한 두 메쏘드를 가지고 있다

  • void ObjectOutputStream.writeObject(Object o) - 객체를 스트림 형태로 출력한다
  • Object ObjectInputStream.readObject() - 스트림으로부터 객체를 생성한다

일단 간단한 객체를 직렬화해 파일에 저장하는 예를 보자

GeneralSerial.java:
import java.io.*;

public class GeneralSerial implements Serializable {
 // 객체는 두 필드를 가지고 있다

 String name;
 int iVal;

 // 생성자
 GeneralSerial() {
  name = "OOPSLA";
  iVal = 5;
 }

 GeneralSerial(String n,int i) {
  name = n;
  iVal = i;
 }

 public static void main(String[] args) throws Exception {
  GeneralSerial cs = new GeneralSerial("HAHA",10);

 /*
    바로 이 부분이 직렬화하는 부분이다. ObjectOutputStream객체를 생성하면서,
    출력 타겟으로 b.ser이라는 이진 파일 출력 객체를 주면, 바이트 스트림은 바로 이 파일에
    작성되게 된다
 */
  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("b.ser"));
  oos.writeObject(cs);
  oos.close();

 /*
    바로 이 부분이 역직렬화하는 부분이다. ObjectInputStream객체를 생성하면서,
    입력 소스로 b.ser이라는 이진 파일 입력 객체를 주면, 바이트 스트림은 바로 이 파일로부터
    읽어들여지게 된다
 */
  ObjectInputStream ois = new ObjectInputStream(new FileInputStream("b.ser"));
  GeneralSerial b = (GeneralSerial)ois.readObject();
  ois.close();

  System.out.println(b.name);
  System.out.println(b.iVal);
 }
}

 

 

직렬화된 객체의 크기를 알아내는 방법은 있는가

C/C++에 sizeof라는 연산자를 떠올리며 이런 생각을 할 수 있는데, 실은 똑 부러진 방법이 없다. 하지만, ByteArray를 사용하면, 크기를 알아낼 수 있다. 그러니까, 객체 직렬화된 바이트 코드의 출력을 파일이 아닌, ByteArray로 하면, 그 바이트 배열의 길이를 읽어들이면 되는 것이다. 대략 다음의 코드와 같다.

 ByteArrayOutputStream baos = new ByteArrayOutputStream();

 ObjectOutputStream oos = new ObjectOutputStream(baos);
 oos.writeObject(obj);

 byte[] bObj = baos.toByteArray();
 System.out.println("길이 = "+bObj.length);
 

선택적인 객체 직렬화 1

객체의 상태 중에서는 직렬화시 그 상태가 저장될 필요가 없는 것이 있을 수 있다. 바이트 코드가 다시 객체로 복원될 때, 적당히 초기값을 넣어주기만 하면 되는 필드는 굳이 직렬화 대상에 포함시킬 필요가 없을 것이다. 예를 들어, 파일에 저장되어 있던 객체가 수정되었는지를 나타내는 boolean 필드로 modified라는 것이 있다고 할 때, 이 값은 굳이 직렬화시킬 필요가 없다. 어차피 메모리 상에서만 의미가 있기 때문이다. 그저, 객체를 다시 복원할 때, 이 값은 무조건 false로 세팅하고 시작하면 되는 것이다. 이러한 필드를 transient 필드라고 한다.

앞선 프로그램 소스 코드에 다음의 필드를 추가하는 등의 수정을 가하자

 transient boolean modified;

 GeneralSerial() {
  name = "OOPSLA";
  iVal = 5;
  modified = true;
 }

 GeneralSerial(String n,int i,boolean m) {
  name = n;
  iVal = i;
  modified = m;
 }

 public static void main(String[] args) throws Exception {
  GeneralSerial cs = new GeneralSerial("HAHA",10,true);
  ....

  // 역직렬화되었을 때, modified 값은 false가 된다.
 }

 

그러면, 저장 후 읽어들일 때 modified값은 false가 되어 있는 것을 확인할 수 있다. 이렇게 직렬화될 필요가 없는 필드는 transient키워드로 직렬화 대상에서 제외시킬 수 있다.

"Transient 필드는 역직렬화되었을 때, 기본 초기값으로 초기화된다. 기본 생성자에서 초기화하는 값이 아님을 주의하기 바란다"

Static 필드의 경우

정적 필드의 경우는 어떻게 될까? 정적 필드는 클래스의 정보이지 객체의 상태가 아니다. 따라서, 직렬화 대상에서 제외된다. 단 이 경우의 초기화는 일반적인 필드의 초기화와는 다르다. 예를 들어 다음의 코드를 보자.

// 필드가 다음과 같이 선언되어 있다.
 transient boolean bVal = true;
 static int id = 100;

// 그리고, 직렬화시켜 파일에 저장한 후, 역직렬화 과정을 거쳐 값을 읽어들이면, 다음과 같다.
 bVal ==> false
 id ==> 100

클래스 정보는 클래스 로더에 의해 읽어들여질 때 초기화되므로, 객체 필드인 bVal과는 다른 결과를 보여준다

선택적인 객체 직렬화 2

이것도 모자라, transient하지 않은 필드까지 내가 알아서 저장하고 싶은 것은 저장하고, 아닌 것은 저장하지 않게 하고 싶은데 방법이 없을까 하는 사람도 있을 것이다. 이를 위해서는 객체 자체가 자신을 직렬화하는 메쏘드를 정의하는 것이다. 이런 특성을 Self-Consistency라고 한다. 자신의 저장 방식을 직접 결정하고 싶은 객체는 다음의 메쏘드를 정확한 프로토타입으로 정의해야 한다

  • private void writeObject(java.io.ObjectOutputStream out) throws IOException
  • private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException

직렬화시, 이 메쏘드가 정의된 객체에 대해서는 저장 방식을 이 메쏘드에 의해 결정한다. 실제로 정의하는 예를 살펴보자

 public class CustomSerial implements SOPJavaInterface {
  String name;
  int iVal;
  ....
  private void writeObject(java.io.ObjectOutputStream out) throws IOException {
   out.writeObject(name);

  }

  private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
  {
   name = (String)in.readObject();
  }
  ....
 }

이렇게 정의되었을 경우, iVal은 transient필드와 같이 취급된다.

직렬화와 상속

직렬화 가능한, 즉 java.io.Serializable 인터페이스를 상속받은 클래스의 하위 클래스들은 모두 직렬화 가능하다

이 말은 사실 당연한 말이다. 이 때, 하위 클래스의 객체를 직렬화시키면, 상위 클래스도 자신의 직렬화 과정을 거치게 되어, 상위 클래스의 상태도 직렬화된다.

직렬화 가능하지 않은 클래스의 하위 클래스가 java.io.Serializable인터페이스를 구현하면 어떻게 될까?

정답은 하위 클래스의 객체가 직렬화될 때, 상위 클래스의 상태는 저장되지 않는다. 직렬화된 후, 다시 역직렬화 과정으로 객체를 복원할 때에는 상위 클래스의 필드의 경우, 상위 클래스의 디폴트 생성자(인자가 없는 생성자)가 암시적으로 호출되어, 값이 초기화되게 된다. 다음의 상위 클래스를 생각해 보자. 이것은 직렬화 가능하지 않은 객체를 생성하는 클래스이다

import java.io.*;

class NonSerialSuper
{
 private String pStr;
 protected String tStr;
 public String bStr;

 public NonSerialSuper() {
  pStr = "Private";
  tStr = "Protected";
  bStr = "Public";
 }

 public NonSerialSuper(String a,String b,String c) {
  pStr = a;
  tStr = b;
  bStr = c;
 }
 public String getPStr() {
  return pStr;
 }
}

 

여기서, 이것을 상속받으면서, 직렬화 가능한 다음의 클래스를 생각해 보자

import java.io.*;

public class SerialChild extends NonSerialSuper implements Serializable {
 String name;
 int iVal;

 SerialChild() {
  super();
  name = "OOPSLA";
  iVal = 5;
 }

 SerialChild(String n,int i) {
  super();
  name = n;
  iVal = i;
 }

 SerialChild(String n,int i,String a,String b,String c) {
  super(a,b,c);
  name = n;
  iVal = i;
 }

 public static void main(String[] args) throws Exception {
  Object cs = new SerialChild("HAHA",10,"A","B","C");

  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.ext"));
  oos.writeObject(cs);
  oos.close();

  ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.ext"));
  SerialChild b = (SerialChild)ois.readObject();
  ois.close();

  System.out.println(b.name);
  System.out.println(b.iVal);
  System.out.println(b.getPStr());
  System.out.println(b.tStr);
  System.out.println(b.bStr);
 }
}

 

main메쏘드에서, 상위 클래스의 세 문자열 필드는 A,B,C로 각각 초기화시켰으나, 실제 직렬화 후, 다시 객체를 복원해 출력해 보면, 결과는 상위 클래스의 생성자에 의해 초기화된 값인 "Private","Protected","Public"이 출력된다

그렇다면, 하위 클래스에 writeObject와 readObject 메쏘드를 정의하면, 상위 클래스의 필드도 직렬화 대상에 포함시킬 수 있지 않을까?

바로 그렇다. 하지만, 하위 클래스에서 상위 클래스의 private필드를 접근할 수 없으므로, 여기에 대한 접근자 메쏘드들(get,set)이 없다면 private필드는 불가능하다. 그리고, 하위 클래스와 상위 클래스가 서로 다른 패키지 내에 있다면, pckage타입의 Modifier를 지니고 있는 필드도 역시 private와 같이 제약이 있을 것이다

 

출처 : http://oopsla.snu.ac.kr/~jwryoo/research/java/serial/

# by 제갈장비 | 2006/09/06 13:32 | 문서-JAVA | 트랙백 | 덧글(0)
트랙백 주소 : http://dojeun.egloos.com/tb/317863
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]

:         :

:

비공개 덧글

◀ 이전 페이지 다음 페이지 ▶

이글루링크 추가하기
()을(를)
이글루링크로 추가하시겠습니까? 추가하시려면 그룹선택을 하세요.
(그룹선택 하지 않는 경우, 최상단 목록에 추가됩니다.)
그룹선택 :
이글루링크 취소