link to Github.

package rule75;

/**
 * 사용자가 지정 직렬화 형식을 사용하면 좋을지 따져 보라
 *
 * 어떤 직렬화 형식이 적절할지 따져보지도 않고 기본 직렬화 형식(Default Serialized Form)을 그대로 받아들이지 마라.
 * 기본 직렬화 형식은 그 객체의 물리적 표현이 논리적 내용과 동일할 때만 적절하다.
 * 즉, 어떤 객체의 가장 효과적인 직렬화 방식은 해당 객체가 나타내는 논리적 데이터만 담아야하며, 물리적 표현과는 무관해야 한다.
 *
 * @author gwon
 * @history
 *          2019. 6. 2. initial creation
 */
public class Rule75 {

}
package rule75;

import java.io.Serializable;

/**
 * 논리적으로 사람의 이름을 표현할 때는 문자열로 구성되므로 문자열로 사람의 이름을 표현하는
 * 이 클래스는 기본 직렬화 형식을 그대로 써도 좋다.
 *
 * '@serial' 태그는 Javadoc에서 제공한다.
 * private 필드여도 Serializable을 구현한 클래스면 접근 가능하므로 Javadoc에 표현해야 한다.
 *
 * @author gwon
 * @history
 *          2019. 6. 2. initial creation
 */
public class NameClassGoodUseDefaultSerializedForm implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 성(last name). null이 될 수 없다.
	 *
	 * @serial
	 */
	private String lastName;
	/**
	 * 이름(first name). null이 될 수 없다.
	 *
	 * @serial
	 */
	private String firstName;
	/**
	 * 중간 이름(middle name). null이 될 수 있다.
	 *
	 * @serial
	 */
	private String middleName;
}
package rule75.compare;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * 링크드리스트로 문자열의 리스트를 표현하는 클래스이다.
 * 이 클래스를 기본 직렬화 형식으로 직렬화 할 경우 모든 연결 리스트 항목과 항목 간 양방향 연결 구조를 모두 직렬화해야 한다. 따라서
 * 1. 너무 많은 공간을 필요로 할 수 있다. (네트워크 속도, 디스크 저장 문제 발생가능)
 * 2. 너무 많은 시간을 소비하는 문제가 생길 수 있다.
 * 3. 스택 오버플로가 발생할 수 있다.
 *
 *
 *
 * 하지만, 이 클래스를 논리적으로만 생각한다면 문자열을 순서대로 저장한 배열이다. 따라서 기본 직렬화 형식이 아닌 사용자 지정 직렬화를 구현해서 직렬화하는 것이 좋다.
 *
 * @author gwon
 * @history
 *          2019. 6. 2. initial creation
 */
public class StringListClassNotGoodUseDefaultSerializedForm implements Serializable {
	private static final long serialVersionUID = 1L;

	private int size = 0;
	private Entry head = null;

	private static class Entry implements Serializable {
		String data;
		Entry next;
		Entry previous;
	}

	public void add(String data) {
		size++;

		if (head == null) {
			head = new Entry();
			head.data = data;
		} else {
			Entry newEntry = new Entry();
			newEntry.data = data;
			head.next = newEntry;
			newEntry.previous = head;
			head = newEntry;
		}

	}

	/**
	 * 기본 직렬화 형식을 사용해서 테스트해본 결과, 3482번째에서 스택 오버플로 발생
	 */
	public static void main(String[] args) throws IOException {
		StringListClassNotGoodUseDefaultSerializedForm stringList =
		        new StringListClassNotGoodUseDefaultSerializedForm();

		for (int i = 0; i < 5000; i++) {
			System.out.println(i);
			stringList.add(String.valueOf(i));

			byte[] serializedMember;
			try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
				try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
					oos.writeObject(stringList);
					serializedMember = baos.toByteArray();
				}
			}

		}
	}
}
package rule75.compare;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * 문자열 리스트는 그저 리스트에 담기는 문자열의 순서이다.
 * 따라서 논리적으로 필요한 데이터만 직렬화하고, 역직렬화한다.
 *
 * '@serialData' 태그는 Javadoc에서 제공한다.
 * private 메서드여도 Serializable을 구현한 클래스면 접근 가능하므로 Javadoc에 표현해야 한다.
 *
 * @author gwon
 * @history
 *          2019. 6. 2. initial creation
 */
public class StringListClassGoodUseUserSerializedForm implements Serializable {
	private static final long serialVersionUID = 1L;

	// transient를 사용하여 필요없는 값은 직렬화에서 제외
	private transient int size = 0;
	private transient Entry head = null;

	// Serializable 구현하지 않음
	private static class Entry {
		String data;
		Entry next;
		Entry previous;
	}

	public void add(String data) {
		size++;

		if (head == null) {
			head = new Entry();
			head.data = data;
		} else {
			Entry newEntry = new Entry();
			newEntry.data = data;
			head.next = newEntry;
			newEntry.previous = head;
			head = newEntry;
		}

	}

	/**
	 * 사용자 지정 직렬화
	 *
	 * @serialData 리스트의 크기가 먼저 기록되고, 그 다음에는 모든 문자열이 순서대로 기록된다.
	 * @param s
	 * @throws IOException
	 */
	private void writeObject(ObjectOutputStream s) throws IOException {
		s.defaultWriteObject(); // 객체의 모든 필드가 transient일 때도 호출하는게 좋다.
		s.writeInt(size);

		for (Entry e = head; e != null; e = e.next) {
			s.writeObject(e.data);
		}
	}

	/**
	 * 사용자 지정 역직렬화
	 *
	 *
	 * @param s
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
		s.defaultReadObject(); // 객체의 모든 필드가 transient일 때도 호출하는게 좋다.

		int numElements = s.readInt();
		for (int i = 0; i < numElements; i++) {
			add((String) s.readObject());
		}
	}

	/**
	 * 사용자 지정 직렬화 형식을 사용해서 테스트해본 결과, 스택 오버플로 발생하지 않음
	 */
	public static void main(String[] args) throws IOException {
		StringListClassGoodUseUserSerializedForm stringList =
		        new StringListClassGoodUseUserSerializedForm();

		for (int i = 0; i < 1000000; i++) {
			System.out.println(i);
			stringList.add(String.valueOf(i));

			byte[] serializedMember;
			try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
				try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
					oos.writeObject(stringList);
					serializedMember = baos.toByteArray();
				}
			}

		}
	}
}
package rule75;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * 객체를 직렬화 할 대는 객체의 상태 전부를 읽는 메서드에 적용할 동기화 수단을 반드시 적용해야 한다.
 *
 * @author gwon
 * @history
 *          2019. 6. 2. initial creation
 */
public class SynchronizedWriteObjectClass implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 모든 메서드를 동기화해서 스레드 안전성을 달성하는 객체가 있다면, 직렬화에도 동기화 사용
	 * 
	 * @param s
	 * @throws IOException
	 */
	private synchronized void writeObject(ObjectOutputStream s) throws IOException {
		s.defaultWriteObject(); // 객체의 모든 필드가 transient일 때도 호출하는게 좋다.
	}

}