Programming/java

[java] serialVersionUID를 선언하는 이유

성일만 2014. 9. 2. 11:18

serialVersionUID를 선언하는 이유


아직 객체 직렬화, 역직렬화에 대해 정확히 알지는 못 하지만 

serialVersionUID가 도대체 왜 선언되어 져야하지? 라는 궁금증을 가졌던 적이 있었다.

100% 이해는 되지 않지만 좋은 정보이기에 가져왔다.


사용자 정의 Exception Class를 구현하는 도중 Class내에 

"private static final long serialVersionUID"를 정의 하지 않을 경우 warning이 발생하여 

궁금증을 해결하고자 정보를 수집하였습니다.


결론부터 말하자면, 모든 Class는 UID를 가지고 있는데 Class의 내용이 변경되면 UID값 역시 같이 바뀌어 버립니다. 직렬화하여 통신하고 UID값으로 통신한게 정상인지 확인하는데 그 값이 바뀌게 되면 다른 Class로 인식을 해버리게 됩니다.

이를 방지하기 위해 고유값으로 미리 명시를 해주는 부분이 바로 

"private static final long serialVersionUID" 이 부분이 되게습니다.

이에 대한 더 많은 정보를 얻고 싶다면 => http://nom3203.egloos.com/2537294


직렬화에 대한 정보를 더 얻고 싶다면 아래 글을 읽어 주시기 바랍니다.

 

기본 정의 체크 => http://blog.naver.com/zzooki/90021730904 


객체 직렬화란?

- 직렬화: Heap에 위치한 객체를 출력 가능한 상태로 만드는 작업

- 역직렬화: 직렬화된 객체를 다시 Heap에 넣기 위한 작업

직렬화의 대상은 객체의 Attribute의 값. (메소드는 그저 주소값만 필요할뿐)

 

- 객체를 IO하기 위해서는 필터스트림인 ObjectInputStream과 ObjectOutputStream이 필요

- 직렬화는 ObjectOutputStream

- 역직렬화는 ObjectInputStream

- 직렬화 대상객체는 java.io.Serializable를 implements한 클래스 객체여야 한다.

 

 

 

 

- 자바의 메모리 구조는 그림과 깉이 되있는데 여기서 보듯이 객체는 Attribute 데이터, 그리고 Method의 주소만 갖고 있을 뿐이다.

따라서 직렬화의 대상은 실체가 없는 Method는 제외하고 Attribute만 직렬화하게 된다.
 

 

 

 

객체 직렬화 하는 방법

- Serializable Interface를 implements한 class의 객체는 뭐든 직렬화가 가능하다.

 

 

ex) 직렬화 대상 클래스

 

import java.io.*; 

public class Member implements Serializable{
    private String name; 
    private transient int age;
    private String address; 
    private transient MyDate birthday;
    ...
}

 

ex) 객체 직렬화 하기

 

public void writeMemberObject(Member m) throws IOException {
    ObjectOutputStream oos = null; 
    try{ 
        oos = new ObjectOutputStream(new FileOutputStream("mem.obj")); // 연결 + 필터 추가 
        oos.writeObject(m); // 객체를 출력하는 메소드: writeObject(Object obj) 
        // write할 때 Exception이 발생하는데 이 때 close를 안하면 스트림이 안닫혀있는 상태로 방치 
    } finally { 
        if(oos!=null) { 
            oos.close(); // 그래서 close로 닫아줌 
        } 
    } 
}





객체 직렬화 피하기 (또는 피해야 되는 경우)

 

- 객체 Instance 변수 앞에 transient 키워드를 붙이면 직렬화 대상에서 제외된다.

- 그런데 왜 직렬화 대상에서 제외할까? 답은...

 

1. 보안적인 측면에서 직렬화 할 때 빼고 싶을 때

 보안 문제로 직렬화 하면 안되는 어트리뷰트가 여기에 해당한다.

 

2. 직렬화 대상이 아닌 객체 type일 경우

 만약 다음과 그림과 같은 A 객체와 B 객체를 직렬화 한다고 해보자.

 

위와 같은 구조에서 A, B 객체들을 직렬화하고 싶을 때 어떻게 할까?

 

답은 B 객체의 멤버 인스턴스인 C 객체에 transient 키워드를 붙여준다.

그러면 직렬화 대상에서 제외되므로 소스 코드를 통채로 고치지 않아도 된다.

 

 

 

 

 

역직렬화 객체를 복원하는 방법

- 객체를 만든 뒤 readObject() 메소드의 리턴하는 Attribute 대입

- 자바가 역직렬화하는 방식은 JVM이 역직렬화 하고자 하는 객체를 만들어서 리턴한다.

 

 

ex)

 

public Member readMemberObject() throws IOException, ClassNotFoundException {
    ObjectInputStream ois = null; 
    Member m = null; 
    try    { 
        ois = new ObjectInputStream(new FileInputStream("mem.obj")); 
        m = (Member)ois.readObject(); 
    } finally { 
        ois.close(); 
        return m; 
    } 
}

 

 

 

그런데 여기서 문제가 발생한다.

만약 역직렬화해서 만든 객체(readObject() 메소드로 불러들인 객체)의 클래스가 변경이 되었다면?

그런데도 역직렬화한 객체를 변경된 클래스의 인스턴스에 대입하려고 한다면?

 

당연히 이 때 JVM은 역직렬화 객체를 대입할 수 없다고 예외를 발생시킨다.

좀 더 자세히 살펴보면 역직렬화한 객체와 새로 변경된 클래스의 serialVersionUID가 일치하지 않아서 발생하는 문제인데

새로 변경된 클래스와 역직렬화하는 객체의 serialVerionUID를 일치시켜주면 된다.

 

그럼 또 어떻게 serialVerionUID를 일치시켜준단 말인가?

답은...

 

클래스를 작성할 때 미리 serialVerionUID를 상수로 고정시켜 놓는다. 이렇게 되면 클래스가 새로 변경이 되도 역직렬화하는 객체와 serialVerionUID가 같기 때문에 예외가 더 이상 발생하지 않는다.

 

 

ex) 직렬화 대상 클래스 (역직렬화 문제 해결)

 

 

import java.io.*; 

public class Member implements Serializable{
    // 직렬화, 역직렬화 시 serialVersionUID 상수가 없으면 JVM이 만들어서 넣어준다. 
    // 있으면 만들지 않는다. 
    static final long serialVersionUID = 100L;
    private String name; 
    private transient int age;
    ...
}

[출처 [Java] Serialize (객체 직렬화)|작성자 데쳄버


'Programming > java' 카테고리의 다른 글

[java] 오버로딩과 오버라이딩  (0) 2014.09.17
[java] JavaSE 환경설정  (0) 2014.09.17
[java] static 키워드  (0) 2014.09.02
[java] MD5 암호화  (0) 2014.06.18
[Java] 입력받은 정수를 역순으로 출력  (0) 2014.02.26