자바

serialVersionUID 필드

직렬화된 객체를 역직렬화할 때는 직렬화했을 때와 같은 클래스를 사용해야 한다. 클래스의 이름이 같더라도 클래스의 내용이 변경되면, 역직렬화는 실패하며 다음과 같은 예외가 발생한다.

serialVersionUID =-91304354354532, local class serialVersionUID =-117324343244534

위 예외의 내용은 직렬화할 때와 역직렬화할 때 사용된 클래스의 serialVersionUID 가 다르다는 것이다. serialVersionUID 는 같은 클래스임을 알려주는 식별자 역할을 하는데, Serializable 인터페이스를 구현한 클래스를 컴파일하면 자동적으로 serialVersionUID  정적 필드가 추가된다. 문제는 클래스를 재컴파일하면 serailVersionUID 의 값이 달라진다는 것이다. 네트워크로 객체를 직렬화하여 전송하는 경우, 보내는 쪽과 받는 쪽이 모두 같은 serialVersionUID 를 갖는 클래스를 가지고 있어야 하는데 한 쪽에서 클래스를 변경해서 재컴파일하면 다른 serialVersionUID 를 가지게 되므로 역직렬화에 실패하게 된다. 

다음 예제는 직렬화할 때 사용했던 클래스를 재컴파일한 후 역직렬화하면 예외가 발생한다는 것을 보여준다. 우선 classC 클래스를 다음과 같이 작성한다. classC 클래스를 작성할 때 serialoVersionUID 필드를 선언하지 않았기 때문에 컴파일 시 자동으로 생성된다. serialVersionUIDExample1 을 실행해서 객체를 파일에 저장한 후 , SerialVersionUIDExample2를 실행하면 정상적으로 객체가 복원되는 것을 확인할 수 있다.

public class ClassC implements Serializable{

    int field1;
    
                      
}

 

public class SerialVersionUIDExample1 {
    
    public static void main(String[] args) throws Exception{
        
        FileOutputStream fos =new FileOutputStream("C:\\Java_Work\\ThisIsJava\\egoing\\src\\egoing5\\Object.datete");
        ObjectOutputStream oos =new ObjectOutputStream(fos);
        ClassC classC =new ClassC();
        classC.field1 =1;
        oos.writeObject(classC);
        oos.flush();
        oos.close();
        fos.close();
        
    }
    
}

 

 

public class SerialVersionUIDExample2 {

    public static void main(String[] args) throws Exception{
        
        FileInputStream fis =new FileInputStream("C:\\Java_Work\\ThisIsJava\\egoing\\src\\egoing5\\Object.datete");
        ObjectInputStream ois =new ObjectInputStream(fis);
        ClassC classC =(ClassC)ois.readObject();
        System.out.println("field1:" + classC.field1);
        
    }
    
}
 

이번에는 ClassC 클래스에 다음과 같이 field2 필드를 추가하고 저장(컴파일) 한다. field2 가 추가되었기 때문에 serialVersionUID 필드값이 변경된다.


public class ClassC implements Serializable{

    int field1;
    int field2;
                      
}
 

파일에 저장된 ClassC 객체를 복원하기 위해 SerialVersinUIDExample2를  실행하면 다음과 같은 예외가 발생한다.

Exception in thread "main" java.io.InvalidClassException: egoing5.ClassC; 
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
    at egoing5.SerialVersionUIDExample2.main(SerialVersionUIDExample2.java:12)

 

 

만약 불가피하게 클래스의 수정이 필요하다면 클래스 작성 시 다음과 같이 serialVersionUID를 명시적으로 선언하면 된다.

 

public class ClassC implements Serializable{

    private static final long serialVersionUID = -9169344732924280591L;
    int field1;
    int field2;
    int field3;
                      
}
 

클래스에 serialVersionUID 가 명시적으로 선언되어 있으면 컴파일 시에 serialVersionUID 필드가 추가되지 않기 때문에 동일한 serialVersionUID 값을 유지할 수 있다. serialVersionUID 의 값은 개발자가 임의로 줄 수 있지만 가능하다면 클래스마다 다른 값을 갖도록 하는 것이 좋다. 

 

writeObject() 와 readObject() 메소드

 

두 클래스가 상속 관계에 있다고 가정해보자. 부모 클래스가 Serializable 인터페이스를 구현하고 있으면 자식 클래스는 Serializable 인터페이스를 구현하지 않아도 자식 객체를 직렬화하면 부모 필드 및 자식 필드가 모두 직렬화된다. 하지만 그 반대로 부모 클래스가 Serializable 인터페이스를 구현하고 있지 않고, 자식 클래스만 Serializable 인터페이스를 구현하고 있다면 자식 객체를 직렬화할 때 부모의 필드는 직렬화에서 제외된다. 이 경우 부모 클래스의 필드를 직렬화하고 싶다면 다음 두 가지 방법 중 하나를 선택해야 한다.

- 부모 클래스가 Serializable 인터페이스를 구현하도록 한다.

- 자식 클래스에서 writeObject() 와 readObject() 메소드를 선언해서 부모 객체의 필드를 직접 출력시킨다.

 

첫 번째 방법이 제일 좋은 방법이 되겠지만 , 부모 클래스의 소스를  수정할 수 없는 경우에는 두 번째 방법을 사용해야 한다. writeObject() 메소드는 직렬화될 때 자동으로 호출되고, readObject() 메소드는 역직렬화될 때 자동적으로 호출된다. 다음은 writeObject() 와 readObject() 메소드의 선언 방법을 보여준다.

두 메소드를 작성할 때 주의할 점은 접근 제한자가 private 가 아니면 자동 호출되지 않기 때문에 반드시 private 를 붙여주어야 한다. writeObject()와 readObject() 메소드의 매개값인 ObjectOutputStream 과 ObjectInputStream 은 다양한 종류의 writeXXX(), readXXX() 메소드를 제공하므로 부모 필드 타입에 맞는 것을 선택해서 사용하면 된다. defaultWriteObject() 와 defaultReadObject() 는 자식 클래스에 정의된 필드들을 모두 직렬화하고 역직렬화 한다.

public class Parent {
    
    public String field1;
}
 

public class Child extends Parent implements Serializable{
    
    public String field2;
    
    private void writeObject(ObjectOutputStream out) throws IOException{
        out.writeUTF(field1);
        out.defaultWriteObject();
    }
    
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
        field1 =in.readUTF();
        in.defaultReadObject();
    }
    
}

 

public class NonSerializableParentExample {
    
    public static void main(String[] args) throws Exception{
        FileOutputStream fos =new FileOutputStream("C:\\Java_Work\\ThisIsJava\\egoing\\src\\egoing5\\Objecdfdt.datete");
        ObjectOutputStream oos =new ObjectOutputStream(fos);
        Child child =new Child();
        child.field1 ="dfdfa";
        child.field2 ="213123";
        
        oos.writeObject(child);
        oos.flush();
        oos.close();
        fos.close();
        
        FileInputStream fis =new FileInputStream("C:\\Java_Work\\ThisIsJava\\egoing\\src\\egoing5\\Objecdfdt.datete");
        ObjectInputStream ois =new ObjectInputStream(fis);
        
        Child v =(Child)ois.readObject();
        System.out.println("field1:" + v.field1);
        System.out.println("field2:" + v.field2);
        ois.close();
        fis.close();
        
    }
}
 

field1:dfdfa
field2:213123
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  머나먼나라

Beware of the wolf in sheep's clothing. (양 가죽을 쓴 늑대를 조심하라.)

댓글 ( 4)

댓글 남기기

작성

자바 목록    more