Comments
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일 때도 호출하는게 좋다. } }