규칙 74 - Serializable 인터페이스를 구현할 때는 신중하라.
JAVA/Effective Java

규칙 74 - Serializable 인터페이스를 구현할 때는 신중하라.

반응형

클래스 선언부에 implements Serializable를 붙히면 간단하게 직렬화 가능 객체를 만들수 있다. 

그렇기 때문에 개발자 입장에서는 Serializable을 붙혀서 직렬화 기능을 만드는 것이 간단하다고 생각할 수 있다.

여기서 먼저 직렬화에 대해서 간단한 예제를 보고 가자.

import java.io.Serializable;

public class Student implements Serializable {

	private static final long serialVersionUID = 1L;
	
	public Student(String name, int number, int height) {
		this.name = name;
		this.number = number;
		this.height = height;
	}
	
	private String name;
	private int number;
	private int height;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getNumber() {
		return number;
	}

	public void setNumber(int number) {
		this.number = number;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}

}

우선 객체의 Serializable 인터페이스를 붙힘으로써 객체 직렬화를 할 클래스를 만드는 준비가 된 것이다. 여기서 함께 사용된 UID는 스트림 고유 식별자로서 모든 직렬화 가능 클래스는 고유한 식별 번호를 가진다. 만약 붙혀주지 않으면 알아서 식별번호를 생성하는데 이 생성되는 식별번호는 클래스 이름, 해당 클래스의 상속 인터페이스, 멤버 변수 등을 영향을 받아 생성되게 된다. 그렇기 때문에 이런 속성이 하나라도 변경된다면 UID가 변경되기 때문에 나중에 호환성이 깨져 실행도중에 InvalidClassException이 발생할 수 있다.

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class Main {
	
	public static void main(String args[]) {
		Student student = new Student("Wedul", 12, 189);
		
		try (
		FileOutputStream fileOut =
		         new FileOutputStream("./test.obj");
		         ObjectOutputStream out = new ObjectOutputStream(fileOut);) {
			
			out.writeObject(student);
			out.close();
			fileOut.close();
			System.out.println("직렬화 성공.");
		} catch (Exception e) {
			System.out.println("직렬화 실패.");
		}
	}
	
}

다음과 같이 코드를 작성하면 성공적으로 클래스를 직렬화하고 파일로 만들어서 내보낸 것을 확인할 수 있다.

그리고 직렬화한 파일을 다시 deserializabable하여 역직렬화 할 수도 있다.

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class Main {
	
	public static void main(String args[]) {
		Student student = null;
		try (
		FileInputStream fileIn =
		         new FileInputStream("./test.obj");
		         ObjectInputStream in = new ObjectInputStream(fileIn);) {
			
			student = (Student) in.readObject();
			
			if (null != student) {
				System.out.println(student.toString());
			}
			
			in.close();
			fileIn.close();
			System.out.println("역직렬화 성공.");
		} catch (Exception e) {
			System.out.println("역직렬화 실패.");
		}
	}
	
}

결과화면

이렇듯 자바 직렬화는 “자바 직렬화 형태의 데이터 교환은 자바 시스템 간의 데이터 교환을 위해서 존재한다.”

 

 


 

그러면 여기서 다시 자바 직렬화시 고려해야 할 문제를 살펴보자.

위에서 보면 알겠지만 Serializable 구현된 클래스는 추후에 변경하기 어려워진다. 왜냐하면 직렬화를 진행하고 나면 생성되는 바이트 스트림 인코딩도 하나의 공개 API가 되기 때문이다. 그렇기 때문에 한번 직렬화를 제공하는 클래스의 경우 쉽게 변경할 수 없게된다. 쉽게 변경하게 될 경우 역직렬화가 실패하게 될 수 있다.

그리고 또 하나 문제는 직렬화를 하게되면 그 클래스 내부에 존재하는 private, package-private 객체도 공개가 되기 때문에 정보은닉의 기본 개념이 사라지게 되는 문제가 발생한다.

그리고 만약 클래스의 Serializable을 구현하지 않기로 하는경우 주의해야 할 것이 있다. 왜냐하면 그 클래스를 상속받는 클래스를 Serializable을 구현하려고자 하는 경우에 불가능할 수도 있기 떄문이다. 다시말하면 상위 클래스에 무인자 생성자가 없다면 직렬화 가능 하위 클래스를 만들수가 없다는 뜻이다. 

그렇기 때문에 계승을 고려해 설계한 직렬화 불가능 클래스에는 무인자 생성자를 제공해주어야 하위클래스가 직렬화를 할 수 있다는 것을 알아야 한다. 

마지막으로 내부 클래스 (inner class)의 경우에는 Serializable을 구현하면 안된다. 왜냐하면 내부 클래스는 바깥 객체에 대한 참조를 보관하고 바깥 유효범위의 지역 변수 값을 보관하기 위해 컴파일러가 자동으로 생성하는 인위생성 필드가 있기 때문이다. 그래서 내부 클래스의 기본 직렬화 형식을 정의할 수 없다. 

 

출처 : 조슈아  블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1)

반응형