item 88 lsucret - JAVA-JIKIMI/EFFECTIVE-JAVA3 GitHub Wiki
item 50์ Date ํ๋๋ฅผ ์ด์ฉํ ๋ถ๋ณ ๊ฐ์ฒด ๋ง๋ค๊ธฐ ์ฝ๋๊ฐ ์๋ค.
๊ฐ๋ณ์ธ Date ํ๋๋ฅผ ์ด์ฉํ๊ธฐ ๋๋ฌธ์, ๋ถ๋ณ์์ ์งํค๊ณ ๋ถ๋ณ์ ์ ์งํ๊ธฐ ์ํด ๋ฐฉ์ด์ฝ๋๊ฐ ๊ธธ์ด์ก์ง๋ง, implements Serializable ๋ฅผ ์ถ๊ฐํ ๋ค๋ก๋ ๋ถ๋ณ์์ ๋ณด์ฅํ ์ ์๊ฒ ๋๋ค.
first draft - ๋ฐฉ์ด์ ๋ณต์ฌ๋ฅผ ์ฌ์ฉํ๋ ๋ถ๋ณ ํด๋์ค
// ์ฝ๋ ์ฐธ์กฐ (https://madplay.github.io/post/write-readobject-methods-defensively)
public final class Period {
private final Date start;
private final Date end;
/**
* @param start ์์ ์๊ฐ
* @param end ์ข
๋ฃ ์๊ฐ; ์์ ์๊ฐ๋ณด๋ค ๋ค์ฌ์ผ ํ๋ค.
* @throws IllegalArgumentException ์์ ์๊ฐ์ด ์ข
๋ฃ ์๊ฐ๋ณด๋ค ๋ฆ์ ๋ ๋ฐ์ํ๋ค.
* @throws NullPointerException start๋ end๊ฐ null์ด๋ฉด ๋ฐ์ํ๋ค.
*/
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
// ๊ฐ๋ณ์ธ Date ํด๋์ค์ ์ํ์ ๋ง๊ธฐ ์ํด ์๋ก์ด ๊ฐ์ฒด๋ก ๋ฐฉ์ด์ ๋ณต์ฌ
this.end = new Date(end.getTime());
if (this.start.compareTo(this.end) > 0) {
throw new IllegalArgumentException(start + "๊ฐ " + end + "๋ณด๋ค ๋ฆ๋ค.");
}
}
public Date start() { return new Date(start.getTime()); }
public Date end() { return new Date(end.getTime()); }
public String toString() { return start + " - " + end; }
// ... ๋๋จธ์ง ์ฝ๋๋ ์๋ต
}
- readObject๊ฐ ์ค์ง์ ์ผ๋ก ๋๋ค๋ฅธ public ์์ฑ์์ด๊ธฐ ๋๋ฌธ์ ๋ถ๋ณ์์ด ๊นจ์ง ์ ์๋ค.
- readObject๋ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ดํธ ์คํธ๋ฆผ์ ๋ฐ๋ ์์ฑ์. ๊ทธ๋ฌ๋ฏ๋ก ๋ถ๋ณ์์ ๊นจ๋จ๋ฆด ์๋๋ก ์์ ์์ฑํ ๋ฐ์ดํธ ์คํธ๋ฆผ์ ๊ฑด๋ค๋ฉด ๋ฌธ์ ๊ฐ ์๊ธด๋ค.
โ readObject ๋ฉ์๋์์๋ ์ธ์์ ์ ํจ์ฑ๊ฒ์ฌ, ํ์ํ๋ค๋ฉด ๋งค๊ฐ๋ณ์๋ฅผ ๋ฐฉ์ด์ ์ผ๋ก ๋ณต์ฌํด์ผ ํ๋ค.
first draft์ ๊ณต๊ฒฉ ์ฝ๋
// ์ฝ๋ ์ฐธ์กฐ (https://madplay.github.io/post/write-readobject-methods-defensively)
public class BogusPeriod {
// ์ง์ง Period ์ธ์คํด์ค์์๋ ๋ง๋ค์ด์ง ์ ์๋ ๋ฐ์ดํธ ์คํธ๋ฆผ,
// ์ ์์ ์ธ Period ์ธ์คํด์ค๋ฅผ ์ง๋ ฌํํ ํ์ ์์ ์์ ํ ๋ฐ์ดํธ ์คํธ๋ฆผ์ด๋ค.
private static final byte[] serializedForm = {
(byte)0xac, (byte)0xed, 0x00, 0x05, 0x73, 0x72, 0x00, 0x06,
0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x40, 0x7e, (byte)0xf8,
... ์๋ต
}
// ์์ ๋นํธ๊ฐ 1์ธ ๋ฐ์ดํธ ๊ฐ๋ค์ byte๋ก ํ๋ณํ ํ๋๋ฐ,
// ์ด์ ๋ ์๋ฐ๊ฐ ๋ฐ์ดํธ ๋ฆฌํฐ๋ด์ ์ง์ํ์ง ์๊ณ byte ํ์
์ ๋ถํธ๊ฐ ์๋(signed) ํ์
์ด๊ธฐ ๋๋ฌธ์ด๋ค.
public static void main(String[] args) {
Period p = (Period) deserialize(serializedForm);
System.out.println(p);
// ์คํ ๊ฒฐ๊ณผ, end๊ฐ start ๋ณด๋ค ๊ณผ๊ฑฐ๋ค. ์ฆ, Period์ ๋ถ๋ณ์์ด ๊นจ์ง๋ค.
// Fri Jan 01 12:00:00 PST 1999 - Sun Jan 01 12:00:00 PST 1984
}
// ์ฃผ์ด์ง ์ง๋ ฌํ ํํ(๋ฐ์ดํธ ์คํธ๋ฆผ)๋ก๋ถํฐ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ๋ฐํํ๋ค.
static Object deserialize(byte[] sf) {
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(sf)) {
try (ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)) {
return objectInputStream.readObject();
}
} catch (IOException | ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
}
}
- ์ด ๋ฌธ์ ๋ฅผ ๊ณ ์น๋ ค๋ฉด ์ญ์ง๋ ฌํ๋ ๊ฐ์ฒด๊ฐ ์ ํจํ์ง ๊ฒ์ฌํด์ผ ํ๋ค.
second draft - ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํ
// ์ฝ๋ ์ฐธ์กฐ (https://madplay.github.io/post/write-readobject-methods-defensively)
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
// ๋ถ๋ณ์์ ๋ง์กฑํ๋์ง ๊ฒ์ฌํ๋ค.
if (start.compareTo(end) > 0) {
throw new InvalidObjectException(start + "after" + end);
}
}
- ์ด์ ๊ณต๊ฒฉ์๊ฐ ํ์ฉ๋์ง ์์ Period ์ธ์คํด์ค๋ฅผ ์์ฑํ๋ ์ผ์ ๋ง์ ์ ์๋ค.
- ๊ทธ๋ฌ๋, ์ ์ Period ์ธ์คํด์ค์์ ์์๋ ๋ฐ์ดํธ ์คํธ๋ฆผ ๋์ private Date ํ๋๋ก์ ์ฐธ์กฐ๋ฅผ ์ถ๊ฐํ๋ฉด ๊ฐ๋ณ Period ์ธ์คํด์ค๋ฅผ ๋ง๋ค์ด๋ผ ์ ์๋ค.
second draft ๊ฐ๋ณ ๊ณต๊ฒฉ ์์
// ์ฝ๋ ์ฐธ์กฐ (https://madplay.github.io/post/write-readobject-methods-defensively)
public class MutablePeriod {
// Period ์ธ์คํด์ค
public final Period period;
// ์์ ์๊ฐ ํ๋ - ์ธ๋ถ์์ ์ ๊ทผํ ์ ์์ด์ผ ํ๋ค.
public final Date start;
// ์ข
๋ฃ ์๊ฐ ํ๋ - ์ธ๋ถ์์ ์ ๊ทผํ ์ ์์ด์ผ ํ๋ค.
public final Date end;
public MutablePeriod() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
// ์ ํจํ Period ์ธ์คํด์ค๋ฅผ ์ง๋ ฌํํ๋ค.
out.writeObject(new Period(new Date(), new Date()));
/*
* ์
์์ ์ธ '์ด์ ๊ฐ์ฒด ์ฐธ์กฐ', ์ฆ ๋ด๋ถ Date ํ๋๋ก์ ์ฐธ์กฐ๋ฅผ ์ถ๊ฐํ๋ค.
* ์์ธ ๋ด์ฉ์ ์๋ฐ ๊ฐ์ฒด ์ง๋ ฌํ ๋ช
์ธ์ 6.4์ ์ฐธ์กฐ.
*/
byte[] ref = { 0x71, 0, 0x7e, 0, 5 }; // ์ฐธ์กฐ #5
bos.write(ref); // ์์(start) ํ๋
ref[4] = 4; // ์ฐธ์กฐ #4
bos.write(ref); // ์ข
๋ฃ(end) ํ๋
// Period ์ญ์ง๋ ฌํ ํ Date ์ฐธ์กฐ๋ฅผ 'ํ์น๋ค'.(????)
ObjectInputStream in = new ObjectInputStream(
new ByteArrayInputStream(bos.toByteArray()));
period = (Period) in.readObject();
start = (Date) in.readObject();
end = (Date) in.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new AssertionError(e);
}
}
public static void main(String[] args) {
MutablePeriod mp = new MutablePeriod();
Period p = mp.period;
Date pEnd = mp.end;
// ์๊ฐ์ ๋๋๋ฆฐ๋ค.
pEnd.setYear(78);
System.out.println(p);
// 60๋
๋๋ก ๋์๊ฐ๋ค.
pEnd.setYear(69);
System.out.println(p);
}
}
// Wed Nov 22 00:21:29 PST 2017 - Wed Nov 22 00:21:29 PST 1978
// Wed Nov 22 00:21:29 PST 2017 - Sat Nov 22 00:21:29 PST 1969
- Period ์ธ์คํด์ค๋ ๋ถ๋ณ์์ ์ ์งํ ์ฑ ์์ฑ๋์ง๋ง, ์๋์ ์ผ๋ก ๋ด๋ถ์ ๊ฐ์ ์์ ํ ์ ์๋ค.
- ๊ณต๊ฒฉ์๋ ์ด ์ธ์คํด์ค๊ฐ ๋ถ๋ณ์ด๋ผ๊ณ ๊ฐ์ ํ๋ ํด๋์ค์ ๋๊ฒจ ๋ณด์ ๋ฌธ์ ๋ฅผ ์ผ์ผํฌ ์ ์๋ค.
โ ๋ฐฉ์ด์ ๋ณต์ฌ๊ฐ ์ถฉ๋ถํ ์ด๋ค์ง์ง ์์๋ค.
โ ๊ฐ์ฒด๋ฅผ ์ญ์ง๋ ฌํํ ๋์๋ ํด๋ผ์ด์ธํธ๊ฐ ์์ ํด์๋ ์ ๋๋ ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ ๊ฐ๋ ํ๋๋ฅผ ๋ชจ๋ ๋ฐ๋์ ๋ฐฉ์ด์ ์ผ๋ก ๋ณต์ฌํด์ผ ํ๋ค. ์ฆ, readObject๋ ๋ถ๋ณ ํด๋์ค ์์ ๋ชจ๋ private ๊ฐ๋ณ ์์๋ฅผ ๋ฐฉ์ด์ ์ผ๋ก ๋ณต์ฌํด์ผ ํ๋ค.
Final Code - ๋ฐฉ์ด์ ๋ณต์ฌ + ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํ
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
// ๊ฐ๋ณ ์์๋ค์ ๋ฐฉ์ด์ ์ผ๋ก ๋ณต์ฌํ๋ค.
start = new Date(start.getTime());
end = new Date(end.getTime());
// ๋ถ๋ณ์์ ๋ง์กฑํ๋์ง ๊ฒ์ฌํ๋ค.
if (start.compareto(end) > 0) {
throw new InvalidObjectException(start + " after " + end);
}
}
// MutablePeriod์ main ๋ฉ์๋ ์ถ๋ ฅ ๊ฒฐ๊ณผ.
// Fri May 31 01:01:06 KST 2019 - Fri May 31 01:01:06 KST 2019
// Fri May 31 01:01:06 KST 2019 - Fri May 31 01:01:06 KST 2019
- ๋ฐฉ์ด์ ๋ณต์ฌ๋ฅผ ์ํด ๊ฐ๋ณ ์์์ final ์์ฝ์ด๋ฅผ ์ ๊ฑฐํ์๋ค.(final ํ๋๋ ๋ฐฉ์ด์ ๋ณต์ฌ ๋ถ๊ฐ)
- ์ด์ MutablePeriod ํด๋์ค๋ ํ์ ์ฐ์ง ๋ชปํ๋ค.
readObject ๋ฉ์๋๋ฅผ ์์ ์์ด ์ฌ์ฉ ๊ฐ๋ฅํ์ง ํ๋จํ๋ ๋ฐฉ๋ฒ
- ๊ฑฐ์ ๋ชจ๋ ๊ฒฝ์ฐ
- ๋ชจ๋ ํ๋์ ๊ฐ์ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ ์ ํจ์ฑ ๊ฒ์ฌ ์์ด ํ๋์ ๋์ ํ๋ public ์์ฑ์(readObject)๋ฅผ ์ถ๊ฐํด๋ ๊ด์ฐฎ์๊ฐ?
โ Yes๋ผ๋ฉด ์ฌ์ฉ ๊ฐ๋ฅ
โ No๋ผ๋ฉด ์ ํจ์ฑ ๊ฒ์ฌ, ๋ฐฉ์ด์ ๋ณต์ฌ ์ํ ํ์
โ No์ผ ๊ฒฝ์ฐ ์ง๋ ฌํ ํ๋ก์ ํจํด์ ์ฌ์ฉํด ์์ ๋ ธ๋ ฅ์ ๊ฒฝ๊ฐ์์ผ์ค ์ ์๋ค.
final ์ด ์๋ ์ง๋ ฌํ ๊ฐ๋ฅ ํด๋์ค๋ผ๋ฉด readObject์ ์์ฑ์์ ๊ณตํต์
- ๋ง์น ์์ฑ์์ฒ๋ผ readObject ๋ฉ์๋๋ ์ฌ์ ์ ๊ฐ๋ฅ ๋ฉ์๋๋ฅผ ํธ์ถํด์๋ ์ ๋๋ค. (???)
- ์ด ๊ท์น์ ์ด๊ฒผ๋๋ฐ ํด๋น ๋ฉ์๋๊ฐ ์ฌ์ ์๋๋ฉด, ํ์ ํด๋์ค์ ์ํ๊ฐ ์์ ํ ์ญ์ง๋ ฌํ๋๊ธฐ ์ ์ ํ์ ํด๋์ค์์ ์ฌ์ ์๋ ๋ฉ์๋๊ฐ ์คํ๋๋ค. โ ํ๋ก๊ทธ๋จ ์ค์๋์ผ๋ก ์ด์ด์ง ๊ฒ
์ ๋ฆฌ
- readObject ๋ฉ์๋๋ฅผ ์์ฑํ ๋๋ public ์์ฑ์๋ฅผ ์์ฑํ๋ ์์ธ๋ก ์ํ๋ค.
- private์ด์ด์ผ ํ๋ ๊ฐ์ฒด ์ฐธ์กฐ ํ๋๋ ํด๋น ๊ฐ์ฒด์ ๋ฐฉ์ด์ ๋ณต์ฌ๊ฐ ํ์ํ๋ค.
- ์ : ๋ถ๋ณ ํด๋์ค ๋ด์ ๊ฐ๋ณ ์์
- ๋ฐฉ์ด์ ๋ณต์ฌ ๋ค์๋ ๋ฐ๋์ ๋ถ๋ณ์ ๊ฒ์ฌ๋ฅผ ํ๋ค.
- ๋ชจ๋ ๋ถ๋ณ์์ ๊ฒ์ฌํด ์ด๊ธ๋ ์ ์ด ์์ผ๋ฉด InvalidObjectException์ ๋์ง๋ค.
- ์ญ์ง๋ ฌํ ํ ๊ฐ์ฒด ๊ทธ๋ํ ์ ์ฒด์ ์ ํจ์ฑ์ ๊ฒ์ฌํด์ผ ํ๋ค๋ฉด ObjectInputValidation ์ธํฐํ์ด์ค๋ฅผ ์ฌ์ฉํ๋ผ
- ์ง์ ์ ์ด๋ ๊ฐ์ ์ ์ด๋ , ์ฌ์ ์ํ ์ ์๋ ๋ฉ์๋๋ ํธ์ถํ์ง ๋ง์.