item 88 junghyunlyoo - JAVA-JIKIMI/EFFECTIVE-JAVA3 GitHub Wiki

๊นจ์ง€๊ธฐ ์‰ฌ์šด ์ง๋ ฌํ™”์—์„œ์˜ ๋ถˆ๋ณ€์‹

item 50์—์„œ๋Š” ๋ถˆ๋ณ€์ธ ๋‚ ์งœ ๋ฒ”์œ„ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐ ๊ฐ€๋ณ€ Date ํ•„๋“œ๋ฅผ ์ด์šฉํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋ถˆ๋ณ€์‹์„ ์ง€ํ‚ค๊ณ  ๋ถˆ๋ณ€์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์ƒ์„ฑ์ž์™€ ์ ‘๊ทผ์ž์—์„œ Date ๊ฐ์ฒด๋ฅผ ๋ฐฉ์–ด์ ์œผ๋กœ ๋ณต์‚ฌํ•˜๋А๋ผ ์ฝ”๋“œ๊ฐ€ ์ƒ๋‹นํžˆ ๊ธธ์–ด์กŒ๋‹ค.

์•„๋ž˜๊ฐ€ ๋ฐ”๋กœ ๊ทธ ํด๋ž˜์Šค๋‹ค.

public final class Period {
    private final Date start;
    private final Date end;

    public Period(Date start, Date end) {
        this.start = new Date(start.getTime());
        this.end = new Date(end.getTime());

        if (this.start.compareTo(this.end) > 0)
            throw new IllegalArgumentException(this.start + "๊ฐ€ " + this.end + "๋ณด๋‹ค ๋Šฆ๋‹ค.");
    }

    public Date start() {
        return new Date(start.getTime());
    }

    public Date end() {
        return new Date(end.getTime());
    }
}

Period ๊ฐ์ฒด์˜ ๋ฌผ๋ฆฌ์  ํ‘œํ˜„์ด ๋…ผ๋ฆฌ์  ํ‘œํ˜„๊ณผ ๋ถ€ํ•ฉํ•˜๋ฏ€๋กœ, ์ด ํด๋ž˜์Šค๋ฅผ ์ง๋ ฌํ™”ํ•˜๊ธฐ ์œ„ํ•ด์„  Serializable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

ํ•˜์ง€๋งŒ ๊ทธ๋Ÿฌ๋ฉด ์ด ํด๋ž˜์Šค์˜ ์ค‘์š”ํ•œ ๋ถˆ๋ณ€์‹์„ ๋”๋Š” ๋ณด์žฅํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ๋œ๋‹ค.


์›์ธ์€ ๋ฐ”๋กœ readObject ๋ฉ”์„œ๋“œ์— ์žˆ๋‹ค. readObject ๋ฉ”์„œ๋“œ๋Š” ์‹ค์งˆ์ ์œผ๋กœ ๋˜ ๋‹ค๋ฅธ public ์ƒ์„ฑ์ž๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋”ฐ๋ผ์„œ ์ƒ์„ฑ์ž๊ฐ€ ์ˆ˜ํ–‰ํ•˜๋Š” ์กฐ๊ฑด๋“ค์„ readObject์—๋„ ๋˜‘๊ฐ™์ด ์ˆ˜ํ–‰ํ•ด์•ผ ํ•œ๋‹ค. (item 50)

๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๊ณต๊ฒฉ์ž๋Š” ์•„์ฃผ ์†์‰ฝ๊ฒŒ ํ•ด๋‹น ํด๋ž˜์Šค์˜ ๋ถˆ๋ณ€์‹์„ ๊นจ๋œจ๋ฆด ์ˆ˜ ์žˆ๋‹ค.

์ง๋ ฌํ™”์—์„œ์˜ ๋ถˆ๋ณ€์‹ ๋ณด์™„

readObject๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ”์ดํŠธ ์ŠคํŠธ๋ฆผ์„ ๋ฐ›๋Š” ์ƒ์„ฑ์ž๋ผ ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฐ”์ดํŠธ ์ŠคํŠธ๋ฆผ์€ ๋ณดํ†ต ์ •์ƒ์ ์œผ๋กœ ์ƒ์„ฑ๋œ ์ธ์Šคํ„ด์Šค๋ฅผ ์ง๋ ฌํ™”ํ•ด์„œ ๋งŒ๋“ค์–ด์ง„๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์ด ๋ฐ”์ดํŠธ ์ŠคํŠธ๋ฆผ์„ ์˜๋„์ ์œผ๋กœ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์ƒ์„ฑํ•˜์—ฌ readObect์— ๊ฑด๋„ค๋ฉด ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๊ฒŒ ๋œ๋‹ค.

์ •์ƒ์ ์ธ ์ƒ์„ฑ์ž๋กœ๋Š” ๋งŒ๋“ค ์ˆ˜ ์—†๋Š” ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.


์ด ๋ฌธ์ œ๋ฅผ ๊ณ ์น˜๋ ค๋ฉด readObject ๋ฉ”์„œ๋“œ๊ฐ€ defaultReadObject๋ฅผ ํ˜ธ์ถœํ•œ ๋‹ค์Œ ์—ญ์ง๋ ฌํ™”๋œ ๊ฐ์ฒด๊ฐ€ ์œ ํšจํ•œ์ง€ ๊ฒ€์‚ฌํ•ด์•ผ ํ•œ๋‹ค.

์ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ์‹คํŒจํ•˜๋ฉด InvalidObjectException์„ ๋˜์ง€๊ฒŒ ํ•˜์—ฌ ์ž˜๋ชป๋œ ์—ญ์ง๋ ฌํ™”๊ฐ€ ์ผ์–ด๋‚˜๋Š” ๊ฒƒ์„ ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค.

private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
    s.defaultReadObject();

    // ๋ถˆ๋ณ€์‹์„ ๋งŒ์กฑํ•˜๋Š”์ง€ ๊ฒ€์‚ฌํ•œ๋‹ค.
    if(start.compareTo(end) > 0) {
        throw new InvalidObjectException(start + "๊ฐ€ " + end + "๋ณด๋‹ค ๋Šฆ๋‹ค.");
    }
}

ํ•˜์ง€๋งŒ ์•„์ง ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค. ์ •์ƒ์ ์œผ๋กœ ์ง๋ ฌํ™”๋œ Period ์ธ์Šคํ„ด์Šค์˜ ๋ฐ”์ดํŠธ ์ŠคํŠธ๋ฆผ ๋์— private Date ํ•„๋“œ๋กœ์˜ ์ฐธ์กฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๊ฐ€๋ณ€ Period ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค์–ด ๋‚ผ ์ˆ˜๊ฐ€ ์žˆ๋‹ค.

๊ณต๊ฒฉ์ž๋Š” ObjectInputStream์—์„œ Period ์ธ์Šคํ„ด์Šค๋ฅผ ์ฝ์€ ํ›„, ์ŠคํŠธ๋ฆผ ๋์— ์ถ”๊ฐ€๋˜์–ด ์žˆ๋Š” '์•…์˜์ ์ธ ๊ฐ์ฒด ์ฐธ์กฐ'๋ฅผ ์ฝ์–ด Period ๊ฐ์ฒด์˜ ๋‚ด๋ถ€ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด ์ฐธ์กฐ๋กœ ์–ป์€ Date ์ธ์Šคํ„ด์Šค๋“ค์„ ๊ฒ€์‚ฌ ์—†์ด ์ˆ˜์ •ํ•ด๋ฒ„๋ฆด ์ˆ˜๋„ ์žˆ์œผ๋‹ˆ, Period ์ธ์Šคํ„ด์Šค์˜ ํ•„๋“œ๋Š” ๋” ์ด์ƒ ๊ฒ€์‚ฌ๋˜์ง€ ์•Š๋Š”๋‹ค.

๋‹ค์Œ์€ ์ด ๊ณต๊ฒฉ์ด ์–ด๋–ป๊ฒŒ ์ด๋ค„์ง€๋Š”์ง€ ๋ณด์—ฌ์ฃผ๋Š” ์˜ˆ๋‹ค.

public class MutablePeriod {
    //Period ์ธ์Šคํ„ด์Šค
    public final Period period;

    //์‹œ์ž‘ ์‹œ๊ฐ ํ•„๋“œ - ์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์—†์–ด์•ผ ํ•œ๋‹ค.
    public final Date start;
    //์ข…๋ฃŒ ์‹œ๊ฐ ํ•„๋“œ - ์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์—†์–ด์•ผ ํ•œ๋‹ค.
    public final Date end;

    public MutablePeriod() {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectArrayOutputStream out = new ObjectArrayOutputStream(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); // Wed Nov 22 00:21:29 PST 2017 - Wed Nov 22 00:21:29 PST 1978

    //60๋…„๋Œ€๋กœ ํšŒ๊ท€
    pEnd.setYear(60);
    System.out.println(p); // Wed Nov 22 00:21:29 PST 2017 - Wed Nov 22 00:21:29 PST 1969
}

์ด ์˜ˆ์—์„œ Period ์ธ์Šคํ„ด์Šค๋Š” ๋ถˆ๋ณ€์‹์„ ์œ ์ง€ํ•œ ์ฑ„ ์ƒ์„ฑ๋์ง€๋งŒ, ์ด๋ ‡๊ฒŒ ์˜๋„์ ์œผ๋กœ ๋‚ด๋ถ€์˜ ๊ฐ’์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด์ฒ˜๋Ÿผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” Period ์ธ์Šคํ„ด์Šค๋ฅผ ํš๋“ํ•œ ๊ณต๊ฒฉ์ž๋Š” ์ด ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ถˆ๋ณ€์ด๋ผ๊ณ  ๊ฐ€์ •ํ•˜๋Š” ํด๋ž˜์Šค์— ๋„˜๊ฒจ ์—„์ฒญ๋‚œ ๋ณด์•ˆ ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.


์ด๋Ÿฌํ•œ ๋ฌธ์ œ์˜ ๊ทผ์›์€ Period์˜ readObject()๊ฐ€ ๋ฐฉ์–ด์  ๋ณต์‚ฌ๋ฅผ ์ถฉ๋ถ„ํžˆ ํ•˜์ง€ ์•Š์€ ๋ฐ ์žˆ๋‹ค.

๊ฐ์ฒด๋ฅผ ์—ญ์ง๋ ฌํ™”ํ•  ๋•Œ๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์†Œ์œ ํ•ด์„œ๋Š” ์•ˆ ๋˜๋Š”, ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ ๊ฐ–๋Š” ํ•„๋“œ๋ฅผ ๋ชจ๋‘ ๋ฐ˜๋“œ์‹œ ๋ฐฉ์–ด์ ์œผ๋กœ ๋ณต์‚ฌํ•ด์•ผ ํ•œ๋‹ค.

๋”ฐ๋ผ์„œ readObject์—์„œ๋Š” ๋ถˆ๋ณ€ ํด๋ž˜์Šค ์•ˆ์˜ ๋ชจ๋“  private ๊ฐ€๋ณ€ ์š”์†Œ๋ฅผ ๋ฐฉ์–ด์ ์œผ๋กœ ๋ณต์‚ฌํ•ด์•ผ ํ•œ๋‹ค.

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 + "๊ฐ€ " + end + "๋ณด๋‹ค ๋Šฆ๋‹ค.");
    }
}

๋ฐฉ์–ด์  ๋ณต์‚ฌ๋ฅผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ณด๋‹ค ์•ž์„œ ์ˆ˜ํ–‰ํ–ˆ๋‹ค.

๋งŒ์•ฝ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ๋ฐฉ์–ด์  ๋ณต์‚ฌ๋ณด๋‹ค ์•ž์— ์žˆ๋‹ค๋ฉด, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ†ต๊ณผํ•œ ํ›„ ๋ฐฉ์–ด์ ์œผ๋กœ ๋ณต์‚ฌํ•˜๊ธฐ ์ „์— ๊ณต๊ฒฉ์ž๊ฐ€ ์ฐธ์กฐ๋ฅผ ํ†ตํ•ด์„œ 

Date๊ฐ’์„ ๋ฐ”๊ฟ”๋ฒ„๋ฆฌ๊ณ  ๊ทธ ํ›„์— ๋ฐฉ์–ด์ ์œผ๋กœ ๋ณต์‚ฌํ•˜๊ฒŒ ๋˜๊ธฐ ๋•Œ๋ฌธ์ธ ๋“ฏ ํ•˜๋‹ค.

ํ•œํŽธ final ํ•„๋“œ๋Š” ๋ฐฉ์–ด์  ๋ณต์‚ฌ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์—, start์™€ end ํ•„๋“œ์—์„œ final ํ•œ์ •์ž๋ฅผ ์ œ๊ฑฐํ•ด์•ผ ํ•œ๋‹ค.

๊ธฐ๋ณธ readObject๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋˜๋Š” ๊ฒฝ์šฐ

transient ํ•„๋“œ๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ํ•„๋“œ์˜ ๊ฐ’์„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์•„, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์—†์ด ํ•„๋“œ์— ๋Œ€์ž…ํ•˜๋Š” public ์ƒ์„ฑ์ž๋ฅผ ์ถ”๊ฐ€ํ•ด๋„ ๊ดœ์ฐฎ์€๊ฐ€?

๊ทธ๋ ‡๋‹ค๋ฉด ๊ธฐ๋ณธ readObject๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋œ๋‹ค.

ํ•˜์ง€๋งŒ ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด ์ปค์Šคํ…€ readObject()๋ฅผ ๋งŒ๋“ค์–ด ์ƒ์„ฑ์ž์—์„œ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์™€ ๋™์ผํ•œ ์ˆ˜์ค€์˜ ๊ฒ€์‚ฌ๋ฅผ ํ•ด์•ผ ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฐฉ์–ด์  ๋ณต์‚ฌ๋Š” ํ•„์ˆ˜์ด๋‹ค.

ํ˜น์€ ์ง๋ ฌํ™” ํ”„๋ก์‹œ ํŒจํ„ด(item 90)์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค. (์ด ํŒจํ„ด์€ ์—ญ์ง๋ ฌํ™”๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐ ํ•„์š”ํ•œ ๋…ธ๋ ฅ์„ ์ƒ๋‹นํžˆ ๊ฒฝ๊ฐํ•ด์ฃผ๋ฏ€๋กœ ์ ๊ทน ๊ถŒ์žฅ๋œ๋‹ค)