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 ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ผ
  • ์ง์ ‘์ ์ด๋“  ๊ฐ„์ ‘์ ์ด๋“ , ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋Š” ํ˜ธ์ถœํ•˜์ง€ ๋ง์ž.