Skip to content

Latest commit

Β 

History

History
192 lines (138 loc) Β· 6.44 KB

item50.md

File metadata and controls

192 lines (138 loc) Β· 6.44 KB

ITEM 50) μ μ‹œμ— 방어적 볡사본을 λ§Œλ“€λΌ

ν΄λΌμ΄μ–ΈνŠΈκ°€ 우리의 λΆˆλ³€μ‹μ„ 깨뜨리렀 ν˜ˆμ•ˆμ΄ λ˜μ–΄ μžˆλ‹€κ³  κ°€μ •ν•˜κ³  λ°©μ–΄μ μœΌλ‘œ ν”„λ‘œκ·Έλž˜λ°ν•΄μ•Ό ν•œλ‹€ !

λΆˆλ³€μ‹ (λΆˆλ³€μ†μ„±) : μ–΄λ–€ 객체의 μƒνƒœκ°€ ν”„λ‘œκ·Έλž˜λ¨Έμ˜ μ˜λ„μ— 맞게 잘 μ •μ˜λ˜μ–΄ μžˆλ‹€κ³  νŒλ‹¨ν•  수 μžˆλŠ” 기쀀을 μ œκ³΅ν•˜λŠ” 속성



λΆˆλ³€μ‹μ„ κΉ¨λœ¨λ¦¬λŠ” 경우 1) 객체의 ν—ˆλ½μ—†μ΄ μ™ΈλΆ€μ—μ„œ μƒμ„±μžλ‘œ 객체 λ‚΄λΆ€μ˜ 값을 λ³€κ²½ν•˜λŠ” 경우

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

    public Period(Date start, Date end) {
        if (start.compareTo(end) > 0) {
            throw new IllegalStateException(start + "κ°€ " + end + " 보닀 λŠ¦μ„ 수 μ—†μŠ΅λ‹ˆλ‹€.");
        }
        this.start = start;
        this.end = end;
    }

    public Date getStart() {
        return start;
    }

    public Date getEnd() {
        return end;
    }
}
  • λΆˆλ³€ 클래슀λ₯Ό μœ„ν•΄ final 클래슀둜 λ§Œλ“€κ³ ,
  • λͺ¨λ“  ν•„λ“œλ₯Ό final 둜 λ§Œλ“€μ—ˆλ‹€.
  • 그리고 μ‹œμž‘ μ‹œκ°„μ΄ 끝 μ‹œκ°„λ³΄λ‹€ 졜근인 κ²½μš°λŠ” μ˜ˆμ™Έλ‘œ λ§‰λŠ”λ‹€.
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");

Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setTime(1000000000);

System.out.println("μ‹œμž‘μ‹œκ°„ : " + dateFormat.format(p.getStart()) + "\nλμ‹œκ°„ : " + dateFormat.format(p.getEnd()));

이미지

  • ν•˜μ§€λ§Œ Date ν΄λž˜μŠ€κ°€ κ°€λ³€ν΄λž˜μŠ€λΌλŠ” 이유둜, μœ„μ˜ λΆˆλ³€μ‹μ€ μ‰½κ²Œ 깨진닀.
  • λ‚ μ§œλ₯Ό ν‘œν˜„ν•  λ–„ JAVA 8 버전에 생긴 Instant λΆˆλ³€ 클래슀λ₯Ό μ‚¬μš©ν•˜λ©΄ ν•΄κ²°ν•  수 μžˆλ‹€.
  • μ™Έμ—λŠ” LocalDateTime, ZoneDateTime 을 μ‚¬μš©ν•  수 μžˆλ‹€.

Date ν΄λž˜μŠ€λŠ” 낑은 API μ΄λ‹ˆ μ‚¬μš©ν•˜μ§€ 말자.

public class Date {...}

public final class Instant {...}
public final class LocalDateTime {...}
public final class ZonedDateTime {...}



λΆˆλ³€μ‹μ„ κΉ¨λœ¨λ¦¬λŠ” 경우 2) 객체의 ν—ˆλ½μ—†μ΄ μ™ΈλΆ€μ—μ„œ setter 둜 객체 λ‚΄λΆ€μ˜ 값을 λ³€κ²½ν•˜λŠ” 경우

        Date start = new Date();
        Date end = new Date();
        Period p1 = new Period(start, end);
        p1.end().setYear(78);

이미 개발된 κ΅¬ν˜„μ— Date 같은 클래슀λ₯Ό μ“°κ³  μžˆλ‹€λ©΄? - λ§€κ°œλ³€μˆ˜μ—μ„œ 받은 κ°€λ³€ λ§€κ°œλ³€μˆ˜ 각각을 방어적 λ³΅μ‚¬ν•˜λΌ.

    // μƒμ„±μž
    public DefensiveCopiedPeriod(Date start, Date end) {
        this.start = new Date(start.getTime());
        this.end = new Date(end.getTime());

        if (start.compareTo(end) > 0) {
            throw new IllegalStateException(start + "κ°€ " + end + " 보닀 λŠ¦μ„ 수 μ—†μŠ΅λ‹ˆλ‹€.");
        }
    }
Date start = new Date();
Date end = new Date();

Period p1 = new Period(start, end);
DefensiveCopiedPeriod p2 = new DefensiveCopiedPeriod(start, end);
end.setTime(1000000000);

System.out.println("<방어적 볡사 μ•ˆν•¨>\n" + "μ‹œμž‘μ‹œκ°„ : " + dateFormat.format(p1.getStart()) + "\nλμ‹œκ°„ : " + dateFormat.format(p1.getEnd()));
System.out.println("<방어적 볡사 함>\n" + "μ‹œμž‘μ‹œκ°„ : " + dateFormat.format(p2.getStart()) + "\nλμ‹œκ°„ : " + dateFormat.format(p2.getEnd()));

compare

  • 방어적 볡사λ₯Ό ν•˜λ©΄ μƒμ„±μžλ‘œ λ„˜κΈ°λŠ” 가변객체λ₯Ό μ‚¬μš©ν•˜μ—¬ μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€μ§€ μ•Šκ³  μƒˆλ‘œμš΄ 객체λ₯Ό λ§Œλ“œλ―€λ‘œ μœ„μ˜ κ³΅κ²©μ—μ„œ μ•ˆμ „ν•˜λ‹€.

μƒμ„±μžμ—μ„œ 볡사와 μœ νš¨μ„± κ²€μ‚¬μ˜ μˆœμ„œ ?

  • λ©€ν‹° μŠ€λ ˆλ“œ ν™˜κ²½μ—μ„œ, 원본 객체의 μœ νš¨μ„±μ„ κ²€μ‚¬ν•˜λŠ” μ°°λ‚˜μ— λ‹€λ₯Έ μŠ€λ ˆλ“œκ°€ 원본 객체λ₯Ό λ³€κ²½ν•  μœ„ν—˜μ΄ μžˆλ‹€.
  • λ°˜λ“œμ‹œ 방어적 볡사 ν›„, μœ νš¨μ„± κ²€μ‚¬ν•˜λŠ” μˆœμ„œλ‘œ μž‘μ„±ν•΄μ•Ό ν•œλ‹€.
  • Time-Of-Check/Time-Of-Use 곡격 (TOCTOU 곡격)

clone() λ©”μ„œλ“œ

  1. μƒμ„±μžμ—μ„œ μƒˆλ‘œμš΄ 객체둜 볡사가 μ•„λ‹Œ clone() 을 μ”λ‹ˆλ‹€.
    public DefensiveCopiedPeriod(Date start, Date end) {
        this.start = (Date) start.clone();
        this.end = (Date) end.clone();

        if (start.compareTo(end) > 0) {
            throw new IllegalStateException(start + "κ°€ " + end + " 보닀 λŠ¦μ„ 수 μ—†μŠ΅λ‹ˆλ‹€.");
        }
    }

  1. Date λŠ” κ°€λ³€ 클래슀기 λ•Œλ¬Έμ—, 이λ₯Ό μƒμ†ν•˜λŠ” 클래슀λ₯Ό λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.
  • μ•…μ˜μ μΈ 리슀트λ₯Ό λ§Œλ“€κ³ , clone λ©”μ„œλ“œλ₯Ό μ˜€λ²„λΌμ΄λ”©ν•˜μ—¬ 객체 μ°Έμ‘°λ₯Ό λ³΄κ΄€ν•©λ‹ˆλ‹€.
public class SubDate extends Date implements Cloneable {

    private List<Date> hackersList = new ArrayList<>();

    public List<Date> getHackersList() {
        return hackersList;
    }

    @Override
    public Object clone() {
        Date badDate = (Date) super.clone();
        hackersList.add(badDate);
        return badDate;
    }
}

  1. μ•„λž˜μ™€ 같이 μ•…μ˜μ μœΌλ‘œ λ§Œλ“  ν•˜μœ„ 클래슀λ₯Ό SubDate λ₯Ό λ„£μ–΄μ„œ 객체 μ°Έμ‘°λ₯Ό μ–»μ–΄μ˜¬ 수 μžˆμŠ΅λ‹ˆλ‹€.
  • μ΄λ ‡κ²Œ 되면 μ–»μ–΄μ˜¨ 참쑰둜 λΆˆλ³€μ‹μ„ 깨뜨릴 수 μžˆμŠ΅λ‹ˆλ‹€.
       Date start2 = new SubDate();
       Date end2 = new SubDate();
       DefensiveCopiedPeriod period = new DefensiveCopiedPeriod(start2, end2);
       List<Date> hackersList = ((SubDate) (period.getEnd())).getHackersList();
       hackersList.get(0).setTime(100000);

       System.out.println("<방어적 볡사 함>\n" + "μ‹œμž‘μ‹œκ°„ : " + dateFormat.format(period.getStart()) + "\nλμ‹œκ°„ : " + dateFormat.format(period.getEnd()));

clone


λ§€κ°œλ³€μˆ˜ (이번 μ˜ˆμ—μ„œλŠ” Date) κ°€ λ‹€λ₯Έ μ‚¬λžŒμ— μ˜ν•΄ ν™•μž₯될 수 μžˆλŠ” νƒ€μž…μ΄λ©΄, clone() 으둜 방어적 볡사λ₯Ό ν•˜λ©΄ μ•ˆλœλ‹€ !
μƒμ„±μžμ™€ 달리 μ ‘κ·Όμž λ©”μ„œλ“œμ—λŠ” clone() 을 μ‚¬μš©ν•΄λ„ λœλ‹€.
  • Period κ°€ 가진 Date κ°€ μƒμ„±μžμ—μ„œ λ“€μ–΄μ™€μ„œ new Date() 둜 λ§Œλ“ λ‹€.
  • λ”°λΌμ„œ ν™•μž₯된 νƒ€μž…μ΄ μ•„λ‹Œ Date μž„μ΄ ν™•μ‹€ν•˜κΈ° 떄문이닀.


방어적 λ³΅μ‚¬μ˜ λ‹€λ₯Έ λͺ©μ  - 객체가 이후 λ³€κ²½λ˜μ—ˆμ„ λ•Œ, ν”„λ‘œκ·Έλž¨μ΄ λ¬Έμ œκ°€ 생길 수 μžˆλŠ”μ§€ κ²€ν† ν•˜λΌ !

  • 예λ₯Ό λ“€μ–΄ 객체가 λ‹€λ₯Έ κ³³μ—μ„œ Map 의 Key κ°’μœΌλ‘œ 쓰이고 μžˆμ—ˆλ‹€λ©΄,
  • 객체가 λ³€κ²½λ˜μ—ˆμ„λ•Œ κ·Έ Map 의 λΆˆλ³€μ‹μ΄ 깨지고, λ¬Έμ œκ°€ 생긴닀.

볡사 λΉ„μš©μ΄ λ„ˆλ¬΄ ν¬κ±°λ‚˜, ν΄λΌμ΄μ–ΈνŠΈκ°€ κ·Έ μš”μ†Œλ₯Ό μˆ˜μ •ν•  일이 μ—†λ‹€λŠ” 것이 ν™•μ‹€ν•˜λ‹€λ©΄ ?

  • 방어적 볡사λ₯Ό μƒλž΅ν•œλ‹€.
  • λ¬Έμ„œν™”μ‹œμ— μ±…μž„μ΄ ν΄λΌμ΄μ–ΈνŠΈμ— μžˆμŒμ„ λͺ…μ‹œν•œλ‹€.