'직렬화'에 해당되는 글 2건

  1. 2010.01.08 직렬화를 할 때 유의해야 할 사항
  2. 2010.01.07 자바 - 직렬화 (Serializable) (5)

자바에서는 직렬화를 통해서 객체를 손쉽게 저장하고 읽어올 수 있다.
하지만 이를 사용할 때에는 한가지 유의해야 할 점이 있다.

한 상황을 생각해보자.

자동차를 관리하는 프로그램을 만들었다고 하자.
이 프로그램은 자동차 객체를 생성하여 직렬화 한 다음에 이를 파일에 저장해서 자동차 목록을 관리한다.
사용자는 아무 불편없이 프로그램을 잘 사용하고 있었다. 그런데 어느날 유명한 자동차 회사에서 자동차에 하늘을 날 수 있는 기능을 추가했다. 그래서 내가 만든 자동차 관리 프로그램에서 자동차가 하늘을 날 수 있는지 없는지를 나타내는 속성이 필요해졌다.
그래서 한치의 망설임도 없이 자동차 클래스에 canFly라는 boolean형의 변수를 추가했다. 그리고 나서 고객에게 업데이트 된 프로그램을 재배포했다.
그런데 배포를 완료한 이후로 고객들에게 전화가 왔다.
프로그램이 제대로 동작하지 않는다는 것이었다.
어떻게 동작하지 않는지 물어보니 새로운 자동차를 등록하는 것은 잘 되는데, 기존 자동차의 목록을 가져오는 것이 안된다고 하는 것이었다.

도대체 문제가 무엇일까? 왜 잘되던 프로그램이 갑자기 제대로 동작하지 않는 것일까?
바로 직렬화 때문이다.
직렬화를 하게 되면 쉽게 객체를 저장할 수 있다는 장점이 있지만 반면에 그 클래스가 바뀔 경우 예외가 발생하는 단점이 있다.

왜냐하면 객체를 직렬화하게 되면 그 객체가 속한 클래스의 버전 ID가 생기게 되는데 이 버전 ID는 클래스의 구조에 따라 다른 값을 가지게 된다.
이 ID를 serialVersionUID라고 하고 static final long으로 할 것을 권하고 있다.
이클립스에서는 Serializable을 구현하게 되면 그 클래스 명에 노란색 밑줄이 생기게 되는데 마우스를 가져가보면 serialVersionUID를 생성하도록 되어있다.

아래의 그림은 Car클래스에 Serializable을 구현했는데 serialVersionUID를 만들지 않았을 경우 발생하는 노란 밑줄을 보여주고 있다.
사용자 삽입 이미지

위와 같은 상황에서 Car에 마우스를 가져가면 아래와 같은 그림이 나온다.
사용자 삽입 이미지
위의 그림에서 2번째 Add generated serial version ID를 클릭하게 되면 자동으로 클래스 구조에 맞게 변수 값을 할당하게 된다.

만약 Serializable을 구현한 클래스에서 serialVersionUID를 생성하지 않는다면 내부적으로 serialVersionUID를 가지고 있게 된다. 그래서 JVM에서 역직렬화를 할 때에 역직렬화하는 객체의 serialVersionUID와 이를 담을 객체의 serialVersionUID를 비교하게 되는데 이 둘의 값이 다르다면 예외를 발생시키게 된다.

그래서 이러한 문제를 해결하기 위해 serialVersionUID를 클래스 안에서 명시하여 특정 값으로 고정시키면 클래스가 바뀌더라도 예외를 발생시키지 않는 것이다. 하지만 이렇게 serialVersionUID의 값을 명시하게 되면 예외가 발생하지는 않지만 프로그래머가 클래스를 변경할 때에 문제가 생기지 않도록 세심한 주으를 기울여야 한다. 이 부분은 철저히 프로그래머가 책임을 져야 하는 부분이다.
신고
Posted by Code-Moon

댓글을 달아 주세요

자바에서는 객체를 손쉽게 저장하거나 전송할 수 있는데, 이를 가능하게 하는 기술이 바로 직렬화입니다.

원래 객체는 힙 안에 존재하게 되는데 직렬화를 하게 되면 그 모양이 약간 변형되게 됩니다. 이렇게 하게 되면 자바에서 저장 및 전송에 아주 편리하고 유리하다는 장점이 있지만, 동시에 다른 프로그램에서는 이를 분석하기 힘들다는 단점이 있습니다. (물론 직렬화를 통해 어떤식으로 변형이 되는지 그 원리만 잘 알고 있다면 다른 프로그램에서도 얼마든지 읽고 쓰는 것이 가능할 것입니다.)


1.Serializable
직렬화를 하기 위해서는 저장하거나 전송할 객체에 Serializable이라는 인터페이스를 구현해 주어야 합니다. 이 Serializable 인터페이스는 구현할 메소드가 하나도 없기 때문에 그냥 implements Serializable만 써주면 됩니다.

아래의 코드가 Serializable을 구현한 Car클래스입니다.
public class Car implements Serializable {
   /* Car 클래스의 내용 */
}


위와 같이 Serializable을 구현해 줘야 그 객체를 직렬화해서 저장하거나 전송이 가능하지, 구현하지 않을 경우에는 런타임 에러가 발생할 것입니다.

객체가 직렬화되면 그 객체 내부에 있는 모든 객체들도 직렬화되게 됩니다. 그런데 Car객체 내부에 있는 다른 객체가 있다 하더라도 전송이나 저장하기 싫은 변수도 있을 수 있습니다. 이럴 경우에는 transient라는 키워드를 사용하면 됩니다.
예를 들어 Car 클래스에 비밀번호를 담고 있는 password 변수가 있어서 이는 전송하고 싶지 않다고 하겠습니다. 그러면 아래와 같이 코드를 작성할 수 있을 것입니다.

public class Car implements Serializable {
   transient private String password;
   /* Car 클래스의 내용 */
}
위와 같이 코드를 작성하면 직렬화를 하더라도 password의 내용은 직렬화되지 않게 됩니다. 이 transient 키워드는 primitive 변수에도 사용이 가능하고, non-primitive변수에도 사용이 가능합니다.


2.스트림(Stream)
자바에서는 여러종류의 스트림(Stream) 클래스가 있는데 이 스트림 클래스를 이용해서 직렬화된 객체를 파일로 저장하거나 전송하는 등 여러가지 작업을 할 수 있는 것입니다.

다음의 코드는 car객체를 data.dat라는 파일에 넣기 위한 코드입니다.
Car car = new Car();
try {
   FileOutputStream fos = new FileOutputStream("data.dat");
   ObjectOutputStream oos = new ObjectOutputStream(fos);
   oos.writeObject(car);
   oos.close();
} catch(Exception e) {
}

코드를 잘 보면 먼저 FileOutputStream 객체를 생성합니다. 이는 파일에 출력하기 위한 스트림입니다.
그리고 ObjectOutputStream 객체인 oos를 생성하는데 이는 객체를 연결하는 스트림이라 생각하면 되겠습니다.
oos가 객체를 저장할 수 있게 하는데 혼자서는 파일이나 소켓에 넘기는 것이 불가능합니다. 그래서 oos를 생성할 때 fos를 인자로 넣음으로써 객체를 파일에 저장할 수 있도록 하는 것입니다.
그러고나면 oos.writeObject(car)를 하게 되는데 이렇게 하면 실제로 파일에 car객체를 저장하는 것입니다. 저장이 끝나면 oos를 닫아줘야 합니다. oos를 닫아주면 그 아래에 있는 fos는 자동으로 닫히기 때문에 신경쓰지 않아도 됩니다.

이를 그림으로 살펴보면 아래와 같습니다.
사용자 삽입 이미지
위 그림에서 보면 car객체를 data.dat라는 파일에 넣기 위해서 먼저 ObjectOutputStream을 거치고 그 다음에 FileOutputStream을 거쳐서 data.dat파일에 들어간다는 것을 알 수 있습니다.

아래의 코드는 data.dat라는 파일에서 car객체에 값을 읽어보는 코드입니다.
Car newCar = new Car();
try {
   FileInputStream fis = new FileInputStream("data.dat");
   ObjectInputStream oos = new ObjectInputStream(fis);
   ois.readObject(car);
   ois.close();
} catch(Exception e) {
}

코드는 크게 바뀐 부분이 없습니다. Ouput대신에 모두 Input으로 바뀌었고, 객체를 쓸 때에는 writeObject()라는 메소드가 사용되었는데 객체에서 읽어올 때에는 readObject()라는 메소드가 사용됩니다.

이에 대한 그림도 아래에 나와 있습니다.
사용자 삽입 이미지
위의 그림을 잘 보면 data.dat라는 파일에서 newCar라는 객체에 불러오는데, 먼저 FileInputStream에서 ObjectInputStream을 거쳐서 newCar에 불러 온다는 것을 알 수 있습니다.


신고
Posted by Code-Moon

댓글을 달아 주세요

  1. BlogIcon 하늘빠 2010.08.17 16:42 신고 Address Modify/Delete Reply

    좋은 글 잘 보았습니다.
    해설이 넘 명쾌하네요.

    • BlogIcon Code-Moon 2010.08.18 19:37 신고 Address Modify/Delete

      오~ 명쾌하다는 칭찬을 해주시다니^^
      감사합니다.
      글 솜씨가 없어서 다른 분들이 이해를 못하실까봐 내심 걱정했었는데, ^^

  2. 창민 2010.09.17 14:06 신고 Address Modify/Delete Reply

    너무 잘쓰셨네요. 이토록 깔끔한 글이라니.. ㅎㅎ
    블로그에 담아갑니다.

  3. 불꽃란 2013.05.27 14:10 신고 Address Modify/Delete Reply

    좋은 설명 감사합니다
    퍼갈게요 ^^

티스토리 툴바