item 90 SeungminLee - JAVA-JIKIMI/EFFECTIVE-JAVA3 GitHub Wiki

item 90

μ§λ ¬ν™”λœ μΈμŠ€ν„΄μŠ€ λŒ€μ‹  직렬화 ν”„λ‘μ‹œ μ‚¬μš©μ„ κ²€ν† ν•˜λΌ

Serializable을 κ΅¬ν˜„ν•˜κΈ°λ‘œ κ²°μ •ν•œ μˆœκ°„ μ–Έμ–΄μ˜ 정상 λ©”μ»€λ‹ˆμ¦˜μΈ μƒμ„±μž μ΄μ™Έμ˜ λ°©λ²•μœΌλ‘œ μΈμŠ€ν„΄μŠ€λ₯Ό 생성할 수 있게 λœλ‹€. 버그와 λ³΄μ•ˆ λ¬Έμ œκ°€ 일어날 κ°€λŠ₯성이 μ»€μ§„λ‹€λŠ” λœ»μ΄λ‹€. ν•˜μ§€λ§Œ 이 μœ„ν—˜μ„ 크게 쀄여쀄 기법이 ν•˜λ‚˜ μžˆλ‹€. λ°”λ‘œ 직렬화 ν”„λ‘μ‹œ νŒ¨ν„΄μ΄λ‹€.

직렬화 ν”„λ‘μ‹œ νŒ¨ν„΄

  • λ°”κΉ₯ 클래슀의 논리적 μƒνƒœλ₯Ό μ •λ°€ν•˜κ²Œ ν‘œν˜„ν•˜λŠ” 쀑첩 클래슀λ₯Ό 섀계해 private static 으둜 μ„ μ–Έν•œλ‹€. β†’ 이 쀑첩 ν΄λž˜μŠ€κ°€ λ°”λ‘œ λ°”κΉ₯ 클래슀의 직렬화 ν”„λ‘μ‹œλ‹€.
  • 쀑첩 클래슀의 μƒμ„±μžλŠ” 단 ν•˜λ‚˜μ—¬μ•Ό ν•˜λ©°, λ°”κΉ₯ 클래슀λ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ λ°›μ•„μ•Ό ν•œλ‹€. β†’ 이 μƒμ„±μžλŠ” λ‹¨μˆœνžˆ 인수둜 λ„˜μ–΄μ˜¨ μΈμŠ€ν„΄μŠ€μ˜ 데이터λ₯Ό λ³΅μ‚¬ν•œλ‹€.
  • 일관성 κ²€μ‚¬λ‚˜ 방어적 볡사가 ν•„μš” μ—†μœΌλ©° 섀계상, 직렬화 ν”„λ‘μ‹œμ˜ κΈ°λ³Έ 직렬화 ν˜•νƒœλŠ” λ°”κΉ₯ 클래슀의 직렬화 ν˜•νƒœλ‘œ 쓰기에 이상적이닀. (?)
  • λ°”κΉ₯ ν΄λž˜μŠ€μ™€ 직렬화 ν”„λ‘μ‹œ λͺ¨λ‘ Serializable을 κ΅¬ν˜„ν•œλ‹€κ³  μ„ μ–Έν•΄μ•Ό ν•œλ‹€.

Period 클래슀λ₯Ό μ΄μš©ν•΄ 직렬화 ν”„λ‘μ‹œ νŒ¨ν„΄μ„ κ΅¬ν˜„ν•΄λ³΄μž.

  • Period 클래슀λ₯Ό μœ„ν•œ 직렬화 ν”„λ‘μ‹œ class SerializationProxy
private static class SerializationProxy implements Serializable {
	private final Date start;
	private final Date end;

	SerializationProxy(Period p) {
		this.start = p.start;
		this.end = p.end;
	}

	private static final long serialVersionID = 24398393023209203L; // 아무 μˆ«μžλ‚˜ λ‹€ λœλ‹€
}
  • 직렬화 ν”„λ‘μ‹œλ₯Ό μ‚¬μš©ν•˜λŠ” λͺ¨λ“  ν΄λž˜μŠ€μ— λ²”μš©μ μœΌλ‘œ μ¨μ€˜μ•Ό ν•˜λŠ” λ©”μ„œλ“œ writeReplaceλ₯Ό λ°”κΉ₯ ν΄λž˜μŠ€μ— μΆ”κ°€
private Object writeReplace() {
	return new SerializationProxy(this);
}

β†’ writeReplaceλŠ” μžλ°”μ˜ 직렬화 μ‹œμŠ€ν…œμ΄ λ°”κΉ₯ 클래슀의 μΈμŠ€ν„΄μŠ€ λŒ€μ‹  SerializationProxy의 μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•˜κ²Œ ν•˜λŠ” 역할을 ν•œλ‹€. (직렬화가 이뀄지기 전에 λ°”κΉ₯ 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό 직렬화 ν”„λ‘μ‹œλ‘œ λ³€ν™˜ν•΄μ€€λ‹€) β†’ 이 λ©”μ†Œλ“œ 덕뢄에 직렬화 μ‹œμŠ€ν…œμ€ κ²°μ½” λ°”κΉ₯ 클래슀의 μ§λ ¬ν™”λœ μΈμŠ€ν„΄μŠ€λ₯Ό 생성해낼 수 μ—†μ§€λ§Œ λΆˆλ³€μ‹μ„ ν›Όμ†ν•˜κ³ λ°”κΉ₯ 클래슀의 μ§λ ¬ν™”λœ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜κ³ μž ν•˜λŠ” μ‹œλ„μ—λŠ” μ–΄λ–»κ²Œ ν•΄μ•Ό ν•˜λŠ”κ°€?

  • λΆˆλ³€μ‹μ„ ν›Όμ†ν•˜κ³ μž ν•˜λŠ” μ‹œλ„λ₯Ό 막기 μœ„ν•΄ λ©”μ„œλ“œ readObjectλ₯Ό λ°”κΉ₯ ν΄λž˜μŠ€μ— μΆ”κ°€
private void readObject(ObjectInputStream stream) {
	throws InvalidObjectException {
		throw new InvalidObjectException("ν”„λ‘μ‹œκ°€ ν•„μš”ν•©λ‹ˆλ‹€.");
}
  • λ§ˆμ§€λ§‰μœΌλ‘œ, λ°”κΉ₯ ν΄λž˜μŠ€μ™€ λ…Όλ¦¬μ μœΌλ‘œ λ™μΌν•œ μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•˜λŠ” λ©”μ„œλ“œ readResolveλ₯Ό μΆ”κ°€ν•œλ‹€. β†’ 이 λ©”μ„œλ“œλŠ” 역직렬화 μ‹œμ— 직렬화 μ‹œμŠ€ν…œμ΄ 직렬화 ν”„λ‘μ‹œλ₯Ό λ‹€μ‹œ λ°”κΉ₯ 클래슀의 μΈμŠ€ν„΄μŠ€λ‘œ λ³€ν™˜ν•˜κ²Œ ν•΄μ€€λ‹€. β†’ 곡개된 APIλ§Œμ„ μ‚¬μš©ν•΄ λ°”κΉ₯ 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•œλ‹€. β†’ μ§λ ¬ν™”λŠ” μƒμ„±μžλ₯Ό μ΄μš©ν•˜μ§€ μ•Šκ³ λ„ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜λŠ” κΈ°λŠ₯을 μ œκ³΅ν•˜λŠ”λ°, 이 νŒ¨λ„‘μ€ μ§λ ¬ν™”μ˜ 이런 κ·œμΉ™μ— μ–΄κΈ‹λ‚œ νŠΉμ„±μ„ 상당 λΆ€λΆ„ μ œκ±°ν•œλ‹€. →즉, 일반 μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€ λ•Œμ™€ λ˜‘κ°™μ€ μƒμ„±μž, 정적 νŒ©ν„°λ¦¬, ν˜Ήμ€ λ‹€λ₯Έ λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•΄μ„œ μ—­μ§λ ¬ν™”λœ μΈμŠ€ν„΄μŠ€ λ₯Ό μƒμ„±ν•œλ‹€.

"μ—­μ§λ ¬ν™”λœ μΈμŠ€ν„΄μŠ€κ°€ ν•΄λ‹Ή 클래슀의 λΆˆλ³€μ‹μ„ λ§Œμ‘±ν•˜λŠ”μ§€ 검사할 또 λ‹€λ₯Έ μˆ˜λ‹¨μ„ κ°•κ΅¬ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€. κ·Έ 클래슀의 정적 νŒ©ν„°λ¦¬λ‚˜ μƒμ„±μžκ°€ λΆˆλ³€μ‹μ„ 확인해주고 μΈμŠ€ν„΄μŠ€ λ©”μ„œλ“œλ“€μ΄ λΆˆλ³€μ‹μ„ 잘 μ§€μΌœμ€€λ‹€λ©΄, 더 ν•΄μ€˜μ•Ό ν•  일은 μ—†λ‹€."

private Object readResolve() {
	return new Period(start, end);
}

방어적 λ³΅μ‚¬μ²˜λŸΌ, 직렬화 ν”„λ‘μ‹œ νŒ¨ν„΄μ€ κ°€μ§œ λ°”μ΄νŠΈ 슀트림 곡격과 λ‚΄λΆ€ ν•„λ“œ νƒˆμ·¨ 곡격을 ν”„λ‘μ‹œ μˆ˜μ€€μ—μ„œ 차단해쀀닀. λ˜ν•œ 직렬화 ν”„λ‘μ‹œλŠ” μ˜ˆμ‹œμ˜ Period ν•„λ“œλ₯Ό final둜 선언해도 λ˜λ―€λ‘œ Period 클래슀λ₯Ό μ§„μ •ν•œ λΆˆλ³€μœΌλ‘œ λ§Œλ“€ μˆ˜λ„ μžˆλ‹€.


"ν”„λ‘μ‹œ νŒ¨ν„΄μ„ μ‚¬μš©ν•˜λ©΄ 이리저리 κ³ λ―Όν•  ν•„μš”κ°€ 거의 μ—†λ‹€! μ–΄λ–€ ν•„λ“œκ°€ 기만적인 직렬화 곡격의 λͺ©ν‘œκ°€ 될지 κ³ λ―Όν•˜μ§€ μ•Šμ•„λ„ 되며, 역직렬화 λ•Œ μœ νš¨μ„± 검사λ₯Ό μˆ˜ν–‰ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€."

직렬화 ν”„λ‘μ‹œ νŒ¨ν„΄μ€ μ—­μ§λ ¬ν™”ν•œ μΈμŠ€ν„΄μŠ€μ™€ μ›λž˜μ˜ μ§λ ¬ν™”λœ μΈμŠ€ν„΄μŠ€μ˜ ν΄λž˜μŠ€κ°€ 달라도 정상 μž‘λ™ν•œλ‹€.

이게 무슨 μš©λ„μΌκΉŒ?

EnumSet은 public μƒμ„±μž 없이 정적 νŒ©ν„°λ¦¬λ“€λ§Œ μ œκ³΅ν•œλ‹€. ν΄λΌμ΄μ–ΈνŠΈμ˜ μž…μž₯μ—μ„œλŠ” λ˜‘κ°™μ§€λ§Œ OpenJDKλ₯Ό 보면 μ—΄κ±° νƒ€μž…μ˜ μ›μ†Œκ°€ 64κ°Έ μ΄ν•˜λ©΄ RegularEnumSet을 μ‚¬μš©ν•˜κ³ , 그보닀 크면 JumboEnumSetλ₯Ό μ‚¬μš©ν•œλ‹€.

  1. μ›μ†Œκ°€ 64개짜리 μ—΄κ±° νƒ€μž…μ„ 가진 EnumSet을 직렬화 ν•œλ‹€.
  2. μ›μ†Œ 5개λ₯Ό μΆ”κ°€ν•˜κ³  (μ›μ†Œκ°€ 64개 이상이 됨) 역직렬화λ₯Ό ν•΄λ³΄μž.
  3. μ²˜λ¦„ 직렬화 된 것은 RegularEnumSetμ΄μ§€λ§Œ, μ—­μ§λ ¬ν™”λŠ” 64개 이상인 JumboEnumSet μΈμŠ€ν„΄μŠ€λ‘œ ν•˜λ©΄ 쒋을 것이닀.
  4. μ΄λŠ” μ‹€μ œλ‘œ EnumSet 직렬화에 μ‚¬μš©λ˜κ³  μžˆλŠ” νŒ¨ν„΄μ΄λ‹€.
private static class SerializationProxy <E extends Enum<E>> implements Serializable {
	// EnumSet μ›μ†Œ νƒ€μž…
	private final Class<E> elementType;
	
	// EnumSet μ•ˆμ˜ μ›μ†Œλ“€ 
	private final Enum<?>[] elements;

	SerializationProxy(EnumSet<E> set) {
		elementType = set.elementType;
		elements = set.toArray(new Enum<?>[0]);
	}
	
	private Object readResolve() {
		EnumSet<E> result = EnumSet.noneOf(elementType);
		for (Enum<?> e : elements) 
			result.add((E)e);
		return result;
	}

	private static final long serialVersionID = 928891839302L;
}

ν”„λ‘μ‹œ νŒ¨ν„΄μ˜ ν•œκ³„

  1. ν΄λΌμ΄μ–ΈνŠΈκ°€ λ©‹λŒ€λ‘œ ν™•μž₯ν•  수 μžˆλŠ” ν΄λž˜μŠ€μ—λŠ” μ μš©ν•  수 μ—†λ‹€.
  2. 객체 κ·Έλž˜ν”„μ— μˆœν™˜μ΄ μžˆλŠ” ν΄λž˜μŠ€μ—λ„ μ μš©ν•  수 μ—†λ‹€. (직렬화 ν”„λ‘μ‹œλ§Œ κ°€μ‘Œμ§€ 아직 객체가 λ§Œλ“€μ–΄μ§€μ§€ μ•Šμ•„μ„œ readReolve μ•ˆμ—μ„œ ν˜ΈμΆœν•˜λ©΄ ClassCastException이 λ°œμƒν•œλ‹€)
  3. 직렬화 ν”„λ‘μ‹œ νŒ¨ν„΄μ€ μ•„μ£Ό κ°•λ ₯ν•œ μ•ˆμ „μ„±μ„ μ œκ³΅ν•˜μ§€λ§Œ 방어적 볡사 λ•Œλ³΄λ‹€ 속도가 λŠλ¦¬λ‹€. (ν™˜κ²½μ— 따라 μ–Όλ§ˆλ‚˜ λŠλ¦°μ§€λŠ” 닀름)

핡심정리

제 3μžκ°€ ν™•μž₯ν•  수 μ—†λŠ” 클래슀라면 κ°€λŠ₯ν•œ ν•œ 직렬화 ν”„λ‘μ‹œ νŒ¨ν„΄μ„ μ‚¬μš©ν•˜μž. 이 νŒ¨ν„΄μ΄ μ•„λ§ˆλ„ μ€‘μš”ν•œ λΆˆλ³€μ‹μ„ μ•ˆμ •μ μœΌλ‘œ μ§λ ¬ν™”ν•΄μ£ΌλŠ” κ°€μž₯ μ‰¬μš΄ 방법일 것이닀.