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
댓글 ( 4)
댓글 남기기