ITEM 11: Overriding hashCode

equalsλ₯Ό μž¬μ •μ˜ν•œ ν΄λž˜μŠ€λŠ” hashCode도 μž¬μ •μ˜ν•΄μ•Ό ν•œλ‹€. λ§Œμ•½ hashCodeλ₯Ό μž¬μ •μ˜ν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄, 일반 κ·œμ•½μ„ μ–΄κΈ°κ²Œ λ˜μ–΄ HashSetμ΄λ‚˜ HashMapκ³Ό 같은 μ»¬λ ‰μ…˜μ˜ μ›μ†Œλ‘œ μ‚¬μš©μ‹œ 문제λ₯Ό μΌμœΌν‚¬ 수 μžˆλ‹€.

  • equals 비ꡐ에 μ‚¬μš©λ˜λŠ” 정보가 λ³€κ²½λ˜μ§€ μ•Šμ•˜λ‹€λ©΄, μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μ‹€ν–‰λ˜λŠ” λ™μ•ˆ κ·Έ 객체의 hashCode λ©”μ„œλ“œλŠ” λͺ‡λ²ˆμ„ ν˜ΈμΆœν•΄λ„ μΌκ΄€λ˜κ²Œ 항상 같은 값을 λ°˜ν™˜ν•΄μ•Όν•œλ‹€.

  • equals(Object)κ°€ 두 객체λ₯Ό κ°™λ‹€κ³  νŒλ‹¨ν–ˆλ‹€λ©΄, 두 객체의 hashCodeλŠ” λ˜‘κ°™μ€ 값을 λ°˜ν™˜ν•΄μ•Όν•œλ‹€.

  • equals(Object)κ°€ 두 객체λ₯Ό λ‹€λ₯΄λ‹€κ³  νŒλ‹¨ν–ˆλ”λΌλ„, 두 객체의 hashCodeκ°€ μ„œλ‘œ λ‹€λ₯Έ 값을 λ°˜ν™˜ν•  ν•„μš”λŠ” μ—†μœΌλ©°, ν•˜μ§€λ§Œ λ‹€λ₯Έ 객체에 λŒ€ν•΄μ„œλŠ” λ‹€λ₯Έ 값을 λ°˜ν™˜ν•΄μ•Ό ν•΄μ‹œν…Œμ΄λΈ”μ˜ μ„±λŠ₯이 μ’‹μ•„μ§„λ‹€. λ§Œμ•½ λͺ¨λ“  객체의 hashCodeκ°€ λ™μΌν•œ 값을 λ°˜ν™˜ν•œλ‹€λ©΄, 평균 μˆ˜ν–‰μ‹œκ°„μ΄ O(1)μ—μ„œ O(n)으둜 느렀져, 객체가 λ§Žμ•„μ§€λ©΄ μ“Έ 수 μ—†κ²Œ λœλ‹€.

λ…Όλ¦¬μ μœΌλ‘œ 같은 κ°μ²΄λŠ” 같은 ν•΄μ‰¬μ½”λ“œλ₯Ό λ°˜ν™˜ν•΄μ•Όν•˜λ©°, hashCode μž¬μ •μ˜λ₯Ό 잘λͺ»ν–ˆμ„ λ•Œ κ°€μž₯ 크게 λ¬Έμ œκ°€ λ˜λŠ” 뢀뢄이닀.

public final class PhoneNumber {
    private final short areaCode, prefix, lineNum;

    public PhoneNumber(short areaCode, short prefix, short lineNum){
        this.areaCode = rangeCheck(areaCode, 999, "μ§€μ—­μ½”λ“œ");
        this.prefix = rangeCheck(prefix, 999,"ν”„λ¦¬ν”½μŠ€");
        this.lineNum = rangeCheck(lineNum, 9999, "κ°€μž…μžλ²ˆν˜Έ");
    }
    private static short rangeCheck(int val, int max, String arg){
        if(val < 0 || val > max){
            throw new IllegalArgumentException(arg+" : "+val);
        }
        return  (short) val;
    }

    @Override
    public boolean equals(Object o){
        if(o == this)
            return true;
        if(!(o instanceof PhoneNumber)){
            return false;
        }

        PhoneNumber pn = (PhoneNumber) o;
        return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode;
    }

}

μ—¬κΈ°μ„œ PhoneNumber ν΄λž˜μŠ€λŠ” hashCodeλ₯Ό μž¬μ •μ˜ν•˜μ§€ μ•Šμ•„, 논리적 λ™μΉ˜μΈ 두 객체가 μ„œλ‘œ λ‹€λ₯Έ ν•΄μ‹œμ½”λ“œλ₯Ό λ°˜ν™˜ν•˜μ—¬ 두 번째 κ·œμ•½μ„ μ§€ν‚€μ§€ λͺ»ν•˜κ²Œλœλ‹€.

μœ„μ˜ hashCodeλŠ” 쒋은 hashCodeλ₯Ό μž‘μ„±ν•˜λŠ” κ°€μž₯ κ°„λ‹¨ν•œ μš”λ Ήμ΄λ‹€. μ—¬κΈ°μ„œλŠ” κΈ°λ³Έ νƒ€μž… ν•„λ“œμ΄κΈ° λ•Œλ¬Έμ— Type.hashCode(f)λ₯Ό μˆ˜ν–‰ν–ˆμ§€λ§Œ, λ§Œμ•½ μ°Έμ‘° νƒ€μž… ν•„λ“œμ΄λ©΄μ„œ, 이 ν΄λž˜μ„œμ˜ equals λ©”μ„œλ“œκ°€ ν•΄λ‹Ήν•„λ“œμ˜ equalsλ₯Ό μž¬κ·€μ μœΌλ‘œ ν˜ΈμΆœν•΄ λΉ„κ΅ν•œλ‹€λ©΄, ν•΄λ‹Ή ν•„λ“œμ˜ hashCodeλ₯Ό μž¬κ·€μ μœΌλ‘œ ν˜ΈμΆœν•˜λ©΄λœλ‹€. λ§Œμ•½ ν•„λ“œμ˜ 값이 null인 경우 0을 μ‚¬μš©ν•˜λ©΄ λœλ‹€.

ν•„λ“œκ°€ 배열이라면, 핡심 μ›μ†Œ 각각을 λ³„λ„μ˜ ν•„λ“œμ²˜λŸΌ 닀루고, 배열에 핡심 μ›μ†Œκ°€ μ—†λ‹€λ©΄ λ‹¨μˆœνžˆ μƒμˆ˜(0)λ₯Ό μ‚¬μš©ν•œλ‹€. λ§Œμ•½ λͺ¨λ“  μ›μ†Œκ°€ 핡심 μ›μ†ŒλΌλ©΄ Arrays.hashCodeλ₯Ό μ‚¬μš©ν•˜λ©΄ λœλ‹€.

μœ„μ™€ 같이 hashCodeλ₯Ό μž‘μ„±ν›„ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μˆ˜ν–‰ν•˜λ©΄ λ™μΉ˜μΈ μΈμŠ€ν„΄μŠ€μ— λŒ€ν•΄ λ˜‘κ°™μ€ ν•΄μ‹œμ½”λ“œλ₯Ό λ°˜ν™˜ν•˜λŠ” 것을 λ³Ό 수 μžˆλ‹€. νŒŒμƒν•„λ“œ(λ‹€λ₯Έ ν•„λ“œλ‘œλΆ€ν„° 계산해 λ‚Ό 수 μžˆλŠ” ν•„λ“œ)λŠ” λͺ¨λ‘ λ¬΄μ‹œν•΄λ„ 되며, equals 비ꡐ에 μ‚¬μš©λ˜μ§€ μ•Šμ€ ν•„λ“œλŠ” λ°˜λ“œμ‹œ μ œμ™Έν•΄μ•Ό ν•œλ‹€.

λ§Œμ•½ ν•΄μ‹œ 좩돌이 λ”μš± 적은 방법을 κΌ­ μ¨μ•Όν•œλ‹€λ©΄, κ΅¬μ•„λ°”μ˜ com.google.common.hash.Hashingarrow-up-right을 μ°Έκ³ ν•˜λŠ” 것이 μ’‹λ‹€.

ν΄λž˜μŠ€κ°€ λΆˆλ³€μ΄κ³  ν•΄μ‹œμ½”λ“œλ₯Ό κ³„μ‚°ν•˜λŠ” λΉ„μš©μ΄ 크닀면, 맀번 μƒˆλ‘œ κ³„μ‚°ν•˜κΈ° λ³΄λ‹€λŠ” μΊμ‹±ν•˜λŠ” 방식을 κ³ λ €ν•˜λŠ” 것이 μ’‹λ‹€.

ν•΄μ‹œμ˜ ν‚€λ‘œ μ‚¬μš©λ˜μ§€ μ•ŠλŠ” 경우라면 λ‹€μŒκ³Ό 같이 hashCodeκ°€ 처음 ν˜ΈμΆœλ λ•Œ κ³„μ‚°ν•˜λŠ” μ§€μ—° μ΄ˆκΈ°ν™” μ „λž΅λ„ μžˆλ‹€. μ΄λ•Œ μŠ€λ ˆλ“œ μ•ˆμ •μ„±κΉŒμ§€ κ³ λ €ν•΄μ•Όν•˜λ©°, μ„±λŠ₯을 λ†’μ΄κ³ μž ν•΄μ‹œμ½”λ“œ κ³„μ‚°μ‹œ 핡심 ν•„λ“œλ₯Ό μƒλž΅ν•΄μ„œλŠ” μ•ˆλœλ‹€.

hashCodeκ°€ λ°˜ν™˜ν•˜λŠ” κ°’μ˜ 생성 κ·œμΉ™μ„ API μ‚¬μš©μžμ—κ²Œ μžμ„Ένžˆ κ³΅ν‘œν•˜μ§€ μ•Šμ•„μ•Ό ν΄λΌμ΄μ–ΈνŠΈκ°€ 이 값에 μ˜μ§€ν•˜μ§€ μ•Šκ²Œλ˜κ³ , 좔후에 계산 방식을 λ°”κΏ€ 수 μžˆλ‹€.

Last updated